From 6d63502a431f5a9cf56d8d38f473d459de7fd52c Mon Sep 17 00:00:00 2001 From: Evgenii Akentev Date: Tue, 16 Apr 2024 16:16:44 +0400 Subject: [PATCH] Add hyperlane-encode-token-message native --- docs/en/pact-functions.md | 15 +++- golden/gas-model/golden | 4 +- src/Crypto/Hash/HyperlaneNatives.hs | 104 ++++++++++++++++++---------- src/Pact/Gas/Table.hs | 6 ++ src/Pact/GasModel/GasTests.hs | 3 +- src/Pact/Interpreter.hs | 2 +- src/Pact/Native.hs | 33 ++++++++- src/Pact/Types/Gas.hs | 6 +- tests/GasModelSpec.hs | 1 + tests/pact/hyperlane.repl | 4 ++ 10 files changed, 132 insertions(+), 46 deletions(-) diff --git a/docs/en/pact-functions.md b/docs/en/pact-functions.md index 7697d28f2..4606c81b9 100644 --- a/docs/en/pact-functions.md +++ b/docs/en/pact-functions.md @@ -1846,6 +1846,17 @@ pact> (hyperlane-decode-token-message "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA {"amount": 0.000000000000000123,"chainId": "4","recipient": KeySet {keys: [da1a339bd82d2c2e9180626a00dc043275deb3ababb27b5738abf6b9dcee8db6],pred: keys-all}} ``` +### hyperlane-encode-token-message {#hyperlane-encode-token-message} + +*x* `object:*` *→* `string` + + +Encode an Hyperlane Token Message object `{recipient:GUARD, amount:DECIMAL, chainId:STRING}` into a base-64-unpadded string. +```lisp +pact> (hyperlane-encode-token-message {"amount": 599.0,"chainId": "1","recipient": "{\"pred\": \"keys-all\", \"keys\":[\"da1a339bd82d2c2e9180626a00dc043275deb3ababb27b5738abf6b9dcee8db6\"]}"}) +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACB4y35cqvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGF7InByZWQiOiAia2V5cy1hbGwiLCAia2V5cyI6WyJkYTFhMzM5YmQ4MmQyYzJlOTE4MDYyNmEwMGRjMDQzMjc1ZGViM2FiYWJiMjdiNTczOGFiZjZiOWRjZWU4ZGI2Il19AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +``` + ### hyperlane-message-id {#hyperlane-message-id} @@ -1854,8 +1865,8 @@ pact> (hyperlane-decode-token-message "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Get the Message Id of a Hyperlane Message object. ```lisp -pact> (hyperlane-message-id {"destinationDomain": 1,"nonce": 325,"originDomain": 626,"recipient": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F","sender": "0x6b622d746f6b656e2d726f75746572","tokenMessage": {"amount": 10000000000000000000.0,"recipient": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F"},"version": 1}) -"0x97d98aa7fdb548f43c9be37aaea33fca79680247eb8396148f1df10e6e0adfb7" +pact> (hyperlane-message-id {"destinationDomain": 1,"nonce": 325,"originDomain": 626,"recipient": "AAAAAAAAAADpgrOqkM0BOY-FQnNzkDXuYlsVcf50GRU","sender": "AAAAAAAAAAAAAAAAf6k4W-ECrD6sKXSD3WIz1is-FJY","messageBody": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAewAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGF7InByZWQiOiAia2V5cy1hbGwiLCAia2V5cyI6WyJkYTFhMzM5YmQ4MmQyYzJlOTE4MDYyNmEwMGRjMDQzMjc1ZGViM2FiYWJiMjdiNTczOGFiZjZiOWRjZWU4ZGI2Il19AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","version": 1}) +"0x3cbd30e222a483f3ad52191674b2f6951adb88636553d10f69a4a002ffd6c8d4" ``` ## REPL-only functions {#repl-lib} diff --git a/golden/gas-model/golden b/golden/gas-model/golden index 7c41279ca..45efbe185 100644 --- a/golden/gas-model/golden +++ b/golden/gas-model/golden @@ -605,8 +605,8 @@ "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a") - 29 - - |- - (hyperlane-message-id {"destinationDomain": 1,"nonce": 325,"originDomain": 626,"recipient": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F","sender": "0x6b622d746f6b656e2d726f75746572","tokenMessage": {"amount": 10000000000000000000.0,"recipient": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F"},"version": 1}) - (hyperlane-message-id {"destinationDomain": 1,"nonce": 325,"originDomain": 626,"recipient": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F","sender": "0x6b622d746f6b656e2d726f75746572","tokenMessage": {"amount": 10000000000000000000.0,"recipient": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"},"version": 1}) + (hyperlane-message-id {"destinationDomain": 1,"nonce": 325,"originDomain": 626,"recipient": "AAAAAAAAAADpgrOqkM0BOY-FQnNzkDXuYlsVcf50GRU","sender": "AAAAAAAAAAAAAAAAf6k4W-ECrD6sKXSD3WIz1is-FJY","messageBody": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAewAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGF7InByZWQiOiAia2V5cy1hbGwiLCAia2V5cyI6WyJkYTFhMzM5YmQ4MmQyYzJlOTE4MDYyNmEwMGRjMDQzMjc1ZGViM2FiYWJiMjdiNTczOGFiZjZiOWRjZWU4ZGI2Il19AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","version": 1}) + (hyperlane-message-id {"destinationDomain": 1,"nonce": 325,"originDomain": 626,"recipient": "AAAAAAAAAADpgrOqkM0BOY-FQnNzkDXuYlsVcf50GRU","sender": "AAAAAAAAAAAAAAAAf6k4W-ECrD6sKXSD3WIz1is-FJY","messageBody": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAewAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGF7InByZWQiOiAia2V5cy1hbGwiLCAia2V5cyI6WyJkYTFhMzM5YmQ4MmQyYzJlOTE4MDYyNmEwMGRjMDQzMjc1ZGViM2FiYWJiMjdiNTczOGFiZjZiOWRjZWU4ZGI2Il19AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAewAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGF7InByZWQiOiAia2V5cy1hbGwiLCAia2V5cyI6WyJkYTFhMzM5YmQ4MmQyYzJlOTE4MDYyNmEwMGRjMDQzMjc1ZGViM2FiYWJiMjdiNTczOGFiZjZiOWRjZWU4ZGI2Il19AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAewAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGF7InByZWQiOiAia2V5cy1hbGwiLCAia2V5cyI6WyJkYTFhMzM5YmQ4MmQyYzJlOTE4MDYyNmEwMGRjMDQzMjc1ZGViM2FiYWJiMjdiNTczOGFiZjZiOWRjZWU4ZGI2Il19AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","version": 1}) - 4 - - (^ 2 longNumber) - 4 diff --git a/src/Crypto/Hash/HyperlaneNatives.hs b/src/Crypto/Hash/HyperlaneNatives.hs index a355168c3..f8fc5b100 100644 --- a/src/Crypto/Hash/HyperlaneNatives.hs +++ b/src/Crypto/Hash/HyperlaneNatives.hs @@ -22,6 +22,7 @@ module Crypto.Hash.HyperlaneNatives -- Implementation of natives , hyperlaneMessageId , hyperlaneDecodeTokenMessage + , hyperlaneEncodeTokenMessage ) where import Control.Lens ((^?), at, _Just, Prism', _1) @@ -30,9 +31,9 @@ import Control.Monad.Except (throwError) import Data.Bifunctor (first) import Data.Binary.Get (Get) import Data.Binary.Get qualified as Bin +import Data.Binary.Put qualified as Bin import Data.ByteString (ByteString) import Data.ByteString qualified as BS -import Data.ByteString.Base16 qualified as Base16 import Data.ByteString.Builder (Builder) import Data.ByteString.Builder qualified as BB import Data.ByteString.Lazy qualified as BL @@ -45,6 +46,7 @@ import Data.Ratio ((%)) import Data.Text (Text) import Data.Text qualified as Text import Data.Text.Encoding qualified as Text +import Data.Text.Read qualified as Text import Data.WideWord.Word256 (Word256(..)) import Data.Word (Word8, Word32) import Ethereum.Misc (keccak256, _getKeccak256Hash, _getBytesN) @@ -52,9 +54,9 @@ import Pact.JSON.Decode qualified as J import Pact.Types.Exp (Literal(..)) import Pact.Types.PactValue (PactValue(PGuard), fromPactValue) import Pact.Types.Pretty (Doc, pretty) -import Pact.Types.Runtime (Object(..), ObjectMap(..), FieldKey, Name, Type(TyAny), _TLiteral, _LInteger, _LString, toTObject, ChainId(..)) +import Pact.Types.Runtime (Object(..), ObjectMap(..), FieldKey, Name, Type(TyAny), _TLiteral, _LInteger, _LString, toTObject, ChainId(..), _LDecimal) import Pact.Types.Term (Term(..), toTerm) -import Pact.Types.Util (decodeBase64UrlUnpadded) +import Pact.Types.Util (decodeBase64UrlUnpadded, encodeBase64UrlUnpadded) ---------------------------------------------- -- Primitives -- @@ -62,7 +64,7 @@ import Pact.Types.Util (decodeBase64UrlUnpadded) hyperlaneMessageId :: Object Name -> Either Doc Text hyperlaneMessageId o = do - hm <- first displayHyperlaneMessageIdError $ decodeHyperlaneMessageObject o + hm <- first displayHyperlaneError $ decodeHyperlaneMessageObject o pure $ getHyperlaneMessageId hm -- | Decode a hyperlane 'TokenMessageERC20' @@ -85,34 +87,42 @@ hyperlaneDecodeTokenMessage i = do pure tm tokenMessageToTerm tm +hyperlaneEncodeTokenMessage :: Object Name -> Either Doc Text +hyperlaneEncodeTokenMessage o = do + tm <- first displayHyperlaneError $ decodeHyperlaneTokenMessageObject o + let encoded = Text.decodeUtf8 $ encodeBase64UrlUnpadded $ BL.toStrict $ Bin.runPut $ Bin.putBuilder $ packTokenMessageERC20 tm + return encoded + ---------------------------------------------- -- Error Types -- ---------------------------------------------- -data HyperlaneMessageIdError - = HyperlaneMessageIdErrorFailedToFindKey FieldKey +data HyperlaneError + = HyperlaneErrorFailedToFindKey FieldKey -- ^ An expected key was not found. - | HyperlaneMessageIdErrorNumberOutOfBounds FieldKey + | HyperlaneErrorNumberOutOfBounds FieldKey -- ^ The number at this field was outside of the expected bounds of its -- type. - | HyperlaneMessageIdErrorBadHexPrefix FieldKey + | HyperlaneErrorBadHexPrefix FieldKey -- ^ Hex textual fields (usually ETH addresses) must be prefixed with "0x" - | HyperlaneMessageIdErrorInvalidHex FieldKey + | HyperlaneErrorInvalidHex FieldKey -- ^ Invalid Hex. We discard error messages from base16-bytestring to - | HyperlaneMessageIdInvalidBase64 FieldKey + | HyperlaneErrorInvalidBase64 FieldKey -- ^ Invalid base64 text field. - | HyperlaneMessageIdIncorrectSize FieldKey Int Int + | HyperlaneErrorIncorrectSize FieldKey Int Int -- ^ Invalid Hex. We discard error messages from base16-bytestring to - -displayHyperlaneMessageIdError :: HyperlaneMessageIdError -> Doc -displayHyperlaneMessageIdError = \case - HyperlaneMessageIdErrorFailedToFindKey key -> "Failed to find key in object: " <> pretty key - HyperlaneMessageIdErrorNumberOutOfBounds key -> "Object key " <> pretty key <> " was out of bounds" - HyperlaneMessageIdErrorBadHexPrefix key -> "Missing 0x prefix on field " <> pretty key - HyperlaneMessageIdErrorInvalidHex key -> "Invalid hex encoding on field " <> pretty key - HyperlaneMessageIdInvalidBase64 key -> "Invalid base64 encoding on field " <> pretty key - HyperlaneMessageIdIncorrectSize key expected actual -> + | HyperlaneErrorInvalidChainId Text + +displayHyperlaneError :: HyperlaneError -> Doc +displayHyperlaneError = \case + HyperlaneErrorFailedToFindKey key -> "Failed to find key in object: " <> pretty key + HyperlaneErrorNumberOutOfBounds key -> "Object key " <> pretty key <> " was out of bounds" + HyperlaneErrorBadHexPrefix key -> "Missing 0x prefix on field " <> pretty key + HyperlaneErrorInvalidHex key -> "Invalid hex encoding on field " <> pretty key + HyperlaneErrorInvalidBase64 key -> "Invalid base64 encoding on field " <> pretty key + HyperlaneErrorIncorrectSize key expected actual -> "Incorrect binary data size " <> pretty key <> ". Expected: " <> pretty expected <> ", but got " <> pretty actual + HyperlaneErrorInvalidChainId msg -> "Failed to decode chainId: " <> pretty msg data HyperlaneDecodeError = HyperlaneDecodeErrorBase64 @@ -244,7 +254,8 @@ unpackTokenMessageERC20 = do getHyperlaneMessageId :: HyperlaneMessage -> Text getHyperlaneMessageId = - encodeHex + Text.decodeUtf8 + . encodeBase64UrlUnpadded . keccak256Hash . BL.toStrict . BB.toLazyByteString @@ -253,27 +264,30 @@ getHyperlaneMessageId = keccak256Hash :: ByteString -> ByteString keccak256Hash = BSS.fromShort . _getBytesN . _getKeccak256Hash . keccak256 -encodeHex :: ByteString -> Text -encodeHex b = "0x" <> Text.decodeUtf8 (Base16.encode b) - -decodeBase64 :: FieldKey -> Text -> Either HyperlaneMessageIdError ByteString +decodeBase64 :: FieldKey -> Text -> Either HyperlaneError ByteString decodeBase64 key s = - first (const $ HyperlaneMessageIdInvalidBase64 key) $ decodeBase64UrlUnpadded $ Text.encodeUtf8 s + first (const $ HyperlaneErrorInvalidBase64 key) $ decodeBase64UrlUnpadded $ Text.encodeUtf8 s -decodeBase64AndValidate :: FieldKey -> Int -> Text -> Either HyperlaneMessageIdError ByteString +decodeBase64AndValidate :: FieldKey -> Int -> Text -> Either HyperlaneError ByteString decodeBase64AndValidate key expected s = do decoded <- decodeBase64 key s unless (BS.length decoded == expected) $ - throwError $ HyperlaneMessageIdIncorrectSize key expected (BS.length decoded) + throwError $ HyperlaneErrorIncorrectSize key expected (BS.length decoded) return decoded ----------------------------------------------- --- Hyperlane Pact Object Decoding -- ----------------------------------------------- +parseChainId :: Text -> Either HyperlaneError Word256 +parseChainId s = do + cid <- first (HyperlaneErrorInvalidChainId . Text.pack) $ Text.decimal s + unless (fst cid >= 0) $ throwError $ HyperlaneErrorInvalidChainId "can't be negative" + return $ fst cid + +------------------------------------------------------ +-- Hyperlane Message Pact Object Decoding -- +------------------------------------------------------ -decodeHyperlaneMessageObject :: Object Name -> Either HyperlaneMessageIdError HyperlaneMessage +decodeHyperlaneMessageObject :: Object Name -> Either HyperlaneError HyperlaneMessage decodeHyperlaneMessageObject o = do let om = _objectMap (_oObject o) @@ -287,6 +301,20 @@ decodeHyperlaneMessageObject o = do pure HyperlaneMessage{..} +------------------------------------------------------------ +-- Hyperlane Token Message Pact Object Decoding -- +------------------------------------------------------------ + +decodeHyperlaneTokenMessageObject :: Object Name -> Either HyperlaneError TokenMessageERC20 +decodeHyperlaneTokenMessageObject o = do + let om = _objectMap (_oObject o) + + tmRecipient <- grabField om "recipient" _LString + tmAmount <- decimalToWord <$> grabField om "amount" _LDecimal + tmChainId <- parseChainId =<< grabField om "chainId" _LString + + pure TokenMessageERC20{..} + ---------------------------------------------- -- Utilities -- ---------------------------------------------- @@ -294,25 +322,29 @@ decodeHyperlaneMessageObject o = do wordToDecimal :: Word256 -> Decimal wordToDecimal w = fromRational (toInteger w % ethInWei) +decimalToWord :: Decimal -> Word256 +decimalToWord d = + round d -- we don't multiply by ethInWei here as the data on chain is already correct + ethInWei :: Num a => a ethInWei = 1_000_000_000_000_000_000 -- 1e18 {-# inline ethInWei #-} -grabField :: Map FieldKey (Term Name) -> FieldKey -> Prism' Literal a -> Either HyperlaneMessageIdError a +grabField :: Map FieldKey (Term Name) -> FieldKey -> Prism' Literal a -> Either HyperlaneError a grabField m key p = case m ^? at key . _Just . _TLiteral . _1 . p of - Nothing -> Left (HyperlaneMessageIdErrorFailedToFindKey key) + Nothing -> Left (HyperlaneErrorFailedToFindKey key) Just a -> Right a -- | Grab a bounded integral value out of the pact object, and make sure -- the integer received is a valid element of that type -grabInt :: forall a. (Integral a, Bounded a) => Map FieldKey (Term Name) -> FieldKey -> Either HyperlaneMessageIdError a +grabInt :: forall a. (Integral a, Bounded a) => Map FieldKey (Term Name) -> FieldKey -> Either HyperlaneError a grabInt m key = do i <- grabField m key _LInteger if i >= fromIntegral @a @Integer minBound && i <= fromIntegral @a @Integer maxBound then do pure (fromIntegral @Integer @a i) else do - throwError (HyperlaneMessageIdErrorNumberOutOfBounds key) + throwError (HyperlaneErrorNumberOutOfBounds key) eof :: Get () eof = do diff --git a/src/Pact/Gas/Table.hs b/src/Pact/Gas/Table.hs index 46e48bc6d..f81c7b8dc 100644 --- a/src/Pact/Gas/Table.hs +++ b/src/Pact/Gas/Table.hs @@ -58,6 +58,7 @@ data GasCostConfig = GasCostConfig , _gasCostConfig_poseidonHashHackAChainLinearGasFactor :: Gas , _gasCostConfig_hyperlaneMessageIdGasPerRecipientOneHundredBytes :: MilliGas , _gasCostConfig_hyperlaneDecodeTokenMessageGasPerOneHundredBytes :: MilliGas + , _gasCostConfig_hyperlaneEncodeTokenMessageGasPerOneHundredBytes :: MilliGas , _gasCostConfig_keccak256GasPerOneHundredBytes :: MilliGas , _gasCostConfig_keccak256GasPerChunk :: MilliGas } @@ -88,6 +89,7 @@ defaultGasConfig = GasCostConfig , _gasCostConfig_poseidonHashHackAChainQuadraticGasFactor = 38 , _gasCostConfig_hyperlaneMessageIdGasPerRecipientOneHundredBytes = MilliGas 47 , _gasCostConfig_hyperlaneDecodeTokenMessageGasPerOneHundredBytes = MilliGas 50 + , _gasCostConfig_hyperlaneEncodeTokenMessageGasPerOneHundredBytes = MilliGas 50 , _gasCostConfig_keccak256GasPerOneHundredBytes = MilliGas 146 , _gasCostConfig_keccak256GasPerChunk = MilliGas 2_120 } @@ -247,6 +249,7 @@ defaultGasTable = ,("poseidon-hash-hack-a-chain", 124) ,("hyperlane-message-id", 2) ,("hyperlane-decode-token-message", 2) + ,("hyperlane-encode-token-message", 2) ,("hash-keccak256",1) ] @@ -351,6 +354,9 @@ tableGasModel gasConfig = GHyperlaneDecodeTokenMessage len -> let MilliGas costPerOneHundredBytes = _gasCostConfig_hyperlaneDecodeTokenMessageGasPerOneHundredBytes gasConfig in MilliGas (costPerOneHundredBytes * div (fromIntegral len) 100) + GHyperlaneEncodeTokenMessage len -> + let MilliGas costPerOneHundredBytes = _gasCostConfig_hyperlaneEncodeTokenMessageGasPerOneHundredBytes gasConfig + in MilliGas (costPerOneHundredBytes * div (fromIntegral len) 100) GKeccak256 chunkBytes -> let MilliGas costPerOneHundredBytes = _gasCostConfig_keccak256GasPerOneHundredBytes gasConfig MilliGas costPerChunk = _gasCostConfig_keccak256GasPerChunk gasConfig diff --git a/src/Pact/GasModel/GasTests.hs b/src/Pact/GasModel/GasTests.hs index 43216e5d9..2b30ddf7f 100644 --- a/src/Pact/GasModel/GasTests.hs +++ b/src/Pact/GasModel/GasTests.hs @@ -2042,8 +2042,7 @@ hyperlaneMessageIdTests :: NativeDefName -> GasUnitTests hyperlaneMessageIdTests = defGasUnitTest $ PactExpression hyperlaneMessageIdExprText Nothing where hyperlaneMessageIdExprText = [text| - (hyperlane-message-id {"destinationDomain": 1,"nonce": 325,"originDomain": 626,"recipient": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F","sender": "0x6b622d746f6b656e2d726f75746572","tokenMessage": {"amount": 10000000000000000000.0,"recipient": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F"},"version": 1}) - (hyperlane-message-id {"destinationDomain": 1,"nonce": 325,"originDomain": 626,"recipient": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F","sender": "0x6b622d746f6b656e2d726f75746572","tokenMessage": {"amount": 10000000000000000000.0,"recipient": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"},"version": 1}) + (hyperlane-message-id {"destinationDomain": 1,"nonce": 325,"originDomain": 626,"recipient": "AAAAAAAAAADpgrOqkM0BOY-FQnNzkDXuYlsVcf50GRU","sender": "AAAAAAAAAAAAAAAAf6k4W-ECrD6sKXSD3WIz1is-FJY","messageBody": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAewAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGF7InByZWQiOiAia2V5cy1hbGwiLCAia2V5cyI6WyJkYTFhMzM5YmQ4MmQyYzJlOTE4MDYyNmEwMGRjMDQzMjc1ZGViM2FiYWJiMjdiNTczOGFiZjZiOWRjZWU4ZGI2Il19AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","version": 1}) |] keccak256Tests :: NativeDefName -> GasUnitTests diff --git a/src/Pact/Interpreter.hs b/src/Pact/Interpreter.hs index 97eb772e6..056ea666c 100644 --- a/src/Pact/Interpreter.hs +++ b/src/Pact/Interpreter.hs @@ -282,7 +282,7 @@ pact411Natives :: [Text] pact411Natives = ["enforce-verifier", "hyperlane-message-id", "hyperlane-decode-token-message"] pact412Natives :: [Text] -pact412Natives = ["hash-keccak256"] +pact412Natives = ["hash-keccak256", "hyperlane-encode-token-message"] initRefStore :: RefStore initRefStore = RefStore nativeDefs diff --git a/src/Pact/Native.hs b/src/Pact/Native.hs index c9c34c14d..ec8d684bd 100644 --- a/src/Pact/Native.hs +++ b/src/Pact/Native.hs @@ -110,7 +110,7 @@ import Pact.Types.Version import Pact.Types.Namespace import Crypto.Hash.Keccak256Native (Keccak256Error(..), keccak256) import Crypto.Hash.PoseidonNative (poseidon) -import Crypto.Hash.HyperlaneNatives (hyperlaneMessageId, hyperlaneDecodeTokenMessage) +import Crypto.Hash.HyperlaneNatives (hyperlaneMessageId, hyperlaneDecodeTokenMessage, hyperlaneEncodeTokenMessage) import qualified Pact.JSON.Encode as J @@ -1624,6 +1624,7 @@ hyperlaneDefs :: NativeModule hyperlaneDefs = ("Hyperlane",) [ hyperlaneMessageIdDef , hyperlaneDecodeTokenMessageDef + , hyperlaneEncodeTokenMessageDef ] hyperlaneMessageIdDef :: NativeDef @@ -1632,7 +1633,7 @@ hyperlaneMessageIdDef = defGasRNative hyperlaneMessageId' (funType tTyString [("x", tTyObjectAny)]) [ - "(hyperlane-message-id {\"destinationDomain\": 1,\"nonce\": 325,\"originDomain\": 626,\"recipient\": \"0x71C7656EC7ab88b098defB751B7401B5f6d8976F\",\"sender\": \"0x6b622d746f6b656e2d726f75746572\",\"tokenMessage\": {\"amount\": 10000000000000000000.0,\"recipient\": \"0x71C7656EC7ab88b098defB751B7401B5f6d8976F\"},\"version\": 1})" + "(hyperlane-message-id {\"destinationDomain\": 1,\"nonce\": 325,\"originDomain\": 626,\"recipient\": \"AAAAAAAAAADpgrOqkM0BOY-FQnNzkDXuYlsVcf50GRU\",\"sender\": \"AAAAAAAAAAAAAAAAf6k4W-ECrD6sKXSD3WIz1is-FJY\",\"messageBody\": \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAewAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGF7InByZWQiOiAia2V5cy1hbGwiLCAia2V5cyI6WyJkYTFhMzM5YmQ4MmQyYzJlOTE4MDYyNmEwMGRjMDQzMjc1ZGViM2FiYWJiMjdiNTczOGFiZjZiOWRjZWU4ZGI2Il19AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\",\"version\": 1})" ] "Get the Message Id of a Hyperlane Message object." where @@ -1672,3 +1673,31 @@ hyperlaneDecodeTokenMessageDef = Left err -> evalError' i err Right term -> pure term _ -> argsError i args + +hyperlaneEncodeTokenMessageDef :: NativeDef +hyperlaneEncodeTokenMessageDef = + defGasRNative + "hyperlane-encode-token-message" + hyperlaneEncodeTokenMessageDef' + (funType tTyObjectAny [("x", tTyString)]) + ["(hyperlane-encode-token-message {recipient:GUARD, amount:DECIMAL, chainId:STRING})"] + "Encode an object into a base-64-unpadded encoded Hyperlane Token Message `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAewAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGF7InByZWQiOiAia2V5cy1hbGwiLCAia2V5cyI6WyJkYTFhMzM5YmQ4MmQyYzJlOTE4MDYyNmEwMGRjMDQzMjc1ZGViM2FiYWJiMjdiNTczOGFiZjZiOWRjZWU4ZGI2Il19AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`." + where + hyperlaneEncodeTokenMessageDef' :: RNativeFun e + hyperlaneEncodeTokenMessageDef' i args = case args of + [TObject o _] -> + computeGas' i (GHyperlaneEncodeTokenMessage (BS.length (getRecipient o))) $ + case hyperlaneEncodeTokenMessage o of + Left err -> evalError' i err + Right msg -> pure $ toTerm $ msg + _ -> argsError i args + + getRecipient :: Object n -> BS.ByteString + getRecipient o = + let mRecipient = do + let om = _objectMap (_oObject o) + om ^? at "recipient" . _Just . _TLiteral . _1 . _LString + in + case mRecipient of + Nothing -> error "couldn't find recipient" + Just t -> T.encodeUtf8 t diff --git a/src/Pact/Types/Gas.hs b/src/Pact/Types/Gas.hs index 6c14392f9..cb46bd194 100644 --- a/src/Pact/Types/Gas.hs +++ b/src/Pact/Types/Gas.hs @@ -184,11 +184,14 @@ data GasArgs -- ^ Cost of the hack-a-chain poseidon hash on this given number of inputs | GHyperlaneMessageId !Int -- ^ Cost of the hyperlane-message-id on this size (in bytes) of the - -- hyperlane TokenMessage Recipient, which is the only variable-length + -- hyperlane Message Body, which is the only variable-length -- part of a HyperlaneMessage | GHyperlaneDecodeTokenMessage !Int -- ^ Cost of hyperlane-decode-token-message on this size (in bytes) of the -- hyperlane TokenMessage base64-encoded string. + | GHyperlaneEncodeTokenMessage !Int + -- ^ Cost of hyperlane-encode-token-message on this size (in bytes) of the + -- hyperlane TokenMessage base64-encoded string. | GKeccak256 !(V.Vector Int) -- ^ Cost of hash-keccak256 given the number of bytes in each chunk. @@ -261,6 +264,7 @@ instance Pretty GasArgs where GPoseidonHashHackAChain len -> "GPoseidonHashHackAChain:" <> pretty len GHyperlaneMessageId len -> "GHyperlaneMessageId:" <> pretty len GHyperlaneDecodeTokenMessage len -> "GHyperlaneDecodeTokenMessage:" <> pretty len + GHyperlaneEncodeTokenMessage len -> "GHyperlaneEncodeTokenMessage:" <> pretty len GKeccak256 chunksBytes -> "GKeccak256:" <> pretty (V.toList chunksBytes) newtype GasLimit = GasLimit ParsedInteger diff --git a/tests/GasModelSpec.hs b/tests/GasModelSpec.hs index cbfc67953..9d8c0c9ad 100644 --- a/tests/GasModelSpec.hs +++ b/tests/GasModelSpec.hs @@ -92,6 +92,7 @@ untestedNativesCheck = do , "list" , "continue" , "hyperlane-decode-token-message" + , "hyperlane-encode-token-message" ]) allGasTestsAndGoldenShouldPass :: Spec diff --git a/tests/pact/hyperlane.repl b/tests/pact/hyperlane.repl index 23cccfde5..ac046523e 100644 --- a/tests/pact/hyperlane.repl +++ b/tests/pact/hyperlane.repl @@ -35,3 +35,7 @@ (env-gas 0) (hyperlane-decode-token-message "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAewAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABfwgIHsgInByZWQiOiAia2V5cy1hbnkiLCAia2V5cyI6IFsgImRhMWEzMzliZDgyZDJjMmU5MTgwNjI2YTAwZGMwNDMyNzVkZWIzYWJhYmIyN2I1NzM4YWJmNmI5ZGNlZThkYjYiLCAiZGExYTMzOWJkODJkMmMyZTkxODA2MjZhMDBkYzA0MzI3NWRlYjNhYmFiYjI3YjU3MzhhYmY2YjlkY2VlOGRiNiIsICJkYTFhMzM5YmQ4MmQyYzJlOTE4MDYyNmEwMGRjMDQzMjc1ZGViM2FiYWJiMjdiNTczOGFiZjZiOWRjZWU4ZGI2IiwgImRhMWEzMzliZDgyZDJjMmU5MTgwNjI2YTAwZGMwNDMyNzVkZWIzYWJhYmIyN2I1NzM4YWJmNmI5ZGNlZThkYjYiLCAiZGExYTMzOWJkODJkMmMyZTkxODA2MjZhMDBkYzA0MzI3NWRlYjNhYmFiYjI3YjU3MzhhYmY2YjlkY2VlOGRiNiIsICJkYTFhMzM5YmQ4MmQyYzJlOTE4MDYyNmEwMGRjMDQzMjc1ZGViM2FiYWJiMjdiNTczOGFiZjZiOWRjZWU4ZGI2IiwgImRhMWEzMzliZDgyZDJjMmU5MTgwNjI2YTAwZGMwNDMyNzVkZWIzYWJhYmIyN2I1NzM4YWJmNmI5ZGNlZThkYjYiLCAiZGExYTMzOWJkODJkMmMyZTkxODA2MjZhMDBkYzA0MzI3NWRlYjNhYmFiYjI3YjU3MzhhYmY2YjlkY2VlOGRiNiIsICJkYTFhMzM5YmQ4MmQyYzJlOTE4MDYyNmEwMGRjMDQzMjc1ZGViM2FiYWJiMjdiNTczOGFiZjZiOWRjZWU4ZGI2IiwgImRhMWEzMzliZDgyZDJjMmU5MTgwNjI2YTAwZGMwNDMyNzVkZWIzYWJhYmIyN2I1NzM4YWJmNmI5ZGNlZThkYjYiLCAiZGExYTMzOWJkODJkMmMyZTkxODA2MjZhMDBkYzA0MzI3NWRlYjNhYmFiYjI3YjU3MzhhYmY2YjlkY2VlOGRiNiIsICJkYTFhMzM5YmQ4MmQyYzJlOTE4MDYyNmEwMGRjMDQzMjc1ZGViM2FiYWJiMjdiNTczOGFiZjZiOWRjZWU4ZGI2IiwgImRhMWEzMzliZDgyZDJjMmU5MTgwNjI2YTAwZGMwNDMyNzVkZWIzYWJhYmIyN2I1NzM4YWJmNmI5ZGNlZThkYjYiLCAiZGExYTMzOWJkODJkMmMyZTkxODA2MjZhMDBkYzA0MzI3NWRlYjNhYmFiYjI3YjU3MzhhYmY2YjlkY2VlOGRiNiIsICJkYTFhMzM5YmQ4MmQyYzJlOTE4MDYyNmEwMGRjMDQzMjc1ZGViM2FiYWJiMjdiNTczOGFiZjZiOWRjZWU4ZGI2IiwgImRhMWEzMzliZDgyZDJjMmU5MTgwNjI2YTAwZGMwNDMyNzVkZWIzYWJhYmIyN2I1NzM4YWJmNmI5ZGNlZThkYjYiLCAiZGExYTMzOWJkODJkMmMyZTkxODA2MjZhMDBkYzA0MzI3NWRlYjNhYmFiYjI3YjU3MzhhYmY2YjlkY2VlOGRiNiIsICJkYTFhMzM5YmQ4MmQyYzJlOTE4MDYyNmEwMGRjMDQzMjc1ZGViM2FiYWJiMjdiNTczOGFiZjZiOWRjZWU4ZGI2IiwgImRhMWEzMzliZDgyZDJjMmU5MTgwNjI2YTAwZGMwNDMyNzVkZWIzYWJhYmIyN2I1NzM4YWJmNmI5ZGNlZThkYjYiLCAiZGExYTMzOWJkODJkMmMyZTkxODA2MjZhMDBkYzA0MzI3NWRlYjNhYmFiYjI3YjU3MzhhYmY2YjlkY2VlOGRiNiIsICJkYTFhMzM5YmQ4MmQyYzJlOTE4MDYyNmEwMGRjMDQzMjc1ZGViM2FiYWJiMjdiNTczOGFiZjZiOWRjZWU4ZGI2IiwgImRhMWEzMzliZDgyZDJjMmU5MTgwNjI2YTAwZGMwNDMyNzVkZWIzYWJhYmIyN2I1NzM4YWJmNmI5ZGNlZThkYjYiIF0gfQAAAAA") (expect "Decoding a message with about 2000 characters should cost 3 gas" 3 (env-gas)) + +(env-gas 0) +(hyperlane-encode-token-message {"amount": 599.0,"chainId": "1","recipient": "{\"pred\": \"keys-all\", \"keys\":[\"da1a339bd82d2c2e9180626a00dc043275deb3ababb27b5738abf6b9dcee8db6\"]}"}) +(expect "Encoding a message with should cost 2 gas" 2 (env-gas))