kadena-io / pact

The Pact Smart Contract Language
https://pact-language.readthedocs.io/en/stable/
BSD 3-Clause "New" or "Revised" License
580 stars 100 forks source link

add hyperlane-message-id native #1335

Closed chessai closed 5 months ago

chessai commented 5 months ago

Tests

Unit test, Haskell:

HyperlaneSpec hyperlane hyperlane-message-id computes the correct message id [✔]

## Repl test, Pact:
- `tests/pact/hyperlane-message-id.repl`

cabal run test:hspec -- --match="pact tests"

PactTestsSpec pact tests ... tests/pact/hyperlane-message-id.repl [✔]


# Gas Benchmark
## Gas Benchmark Code
```haskell
import Gauge.Main

benchy :: IO ()
benchy = do
  defaultMain
    [ bgroup "hyperlane"
        [ bench "0" $ whnf getHyperlaneMessageId hm0
        , bench "10" $ whnf getHyperlaneMessageId hm10
        , bench "20" $ whnf getHyperlaneMessageId hm20
        , bench "50" $ whnf getHyperlaneMessageId hm50
        , bench "100" $ whnf getHyperlaneMessageId hm100
        , bench "500" $ whnf getHyperlaneMessageId hm500
        , bench "1000" $ whnf getHyperlaneMessageId hm1000
        , bench "10000" $ whnf getHyperlaneMessageId hm10000
        ]
    ]

hm0, hm10, hm20, hm50, hm100, hm500, hm1000, hm10000 :: HyperlaneMessage
hm0 = genHM 0
hm10 = genHM 10
hm20 = genHM 20
hm50 = genHM 50
hm100 = genHM 100
hm500 = genHM 500
hm1000 = genHM 1_000
hm10000 = genHM 10_000

-- | Create a HyperlaneMessage where all bytes are 0 except for the only
--   variable length field, the TokenMessage recipient.
genHM :: Int -> HyperlaneMessage
genHM recipientSize = HyperlaneMessage
  { hmVersion = 0
  , hmNonce = 0
  , hmOriginDomain = 0
  , hmSender = BS.replicate 32 0
  , hmDestinationDomain = 0
  , hmRecipient = BS.replicate 32 0
  , hmTokenMessage = TokenMessageERC20
      { tmRecipient = Text.pack $ List.replicate recipientSize 'A'
      , tmAmount = 0
      , tmChainId = Nothing
      }
  }
{-# noinline genHM #-}

Gas Benchmark Results

benchmarking hyperlane/0 ... took 35.43 s, total 329090 iterations
benchmarked hyperlane/0
time                 2.876 μs   (2.770 μs .. 2.981 μs)
                     0.997 R²   (0.992 R² .. 0.999 R²)
mean                 3.051 μs   (3.030 μs .. 3.071 μs)
std dev              34.66 ns   (26.64 ns .. 46.71 ns)

benchmarking hyperlane/10 ... took 38.33 s, total 329090 iterations
benchmarked hyperlane/10
time                 2.962 μs   (2.936 μs .. 2.980 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 3.025 μs   (3.019 μs .. 3.032 μs)
std dev              10.88 ns   (7.858 ns .. 15.84 ns)

benchmarking hyperlane/20 ... took 38.35 s, total 329090 iterations
benchmarked hyperlane/20
time                 3.033 μs   (2.991 μs .. 3.080 μs)
                     1.000 R²   (0.999 R² .. 1.000 R²)
mean                 3.062 μs   (3.057 μs .. 3.070 μs)
std dev              10.53 ns   (5.386 ns .. 17.42 ns)

benchmarking hyperlane/50 ... took 37.90 s, total 313406 iterations
benchmarked hyperlane/50
time                 2.247 μs   (981.0 ns .. 3.185 μs)
                     0.713 R²   (0.249 R² .. 0.998 R²)
mean                 3.176 μs   (3.073 μs .. 3.559 μs)
std dev              303.8 ns   (33.02 ns .. 505.6 ns)
variance introduced by outliers: 28% (moderately inflated)

benchmarking hyperlane/100 ... took 38.02 s, total 298469 iterations
benchmarked hyperlane/100
time                 3.288 μs   (3.086 μs .. 3.389 μs)
                     0.996 R²   (0.986 R² .. 0.999 R²)
mean                 3.312 μs   (3.294 μs .. 3.333 μs)
std dev              32.87 ns   (21.87 ns .. 50.43 ns)

benchmarking hyperlane/500 ... took 37.08 s, total 245502 iterations
benchmarked hyperlane/500
time                 3.707 μs   (3.475 μs .. 3.928 μs)
                     0.992 R²   (0.981 R² .. 0.998 R²)
mean                 4.024 μs   (3.984 μs .. 4.065 μs)
std dev              69.26 ns   (54.88 ns .. 83.66 ns)

benchmarking hyperlane/1000 ... took 36.55 s, total 212036 iterations
benchmarked hyperlane/1000
time                 4.540 μs   (4.356 μs .. 4.699 μs)
                     0.997 R²   (0.994 R² .. 0.999 R²)
mean                 4.785 μs   (4.758 μs .. 4.824 μs)
std dev              53.55 ns   (35.82 ns .. 77.57 ns)

benchmarking hyperlane/10000 ... took 29.65 s, total 48854 iterations
benchmarked hyperlane/10000
time                 20.96 μs   (20.60 μs .. 21.37 μs)
                     0.999 R²   (0.998 R² .. 1.000 R²)
mean                 20.77 μs   (20.74 μs .. 20.83 μs)
std dev              76.68 ns   (48.73 ns .. 110.9 ns)

20240212_15h53m30s_grim

Going off of these numbers, assuming 1 milligas is 2.5ns, that means our cost is roughly 1120.8 + 0.4726 * byte_length(x), where x is the TokenMessage recipient. To avoid rounding errors due to the small cost per byte, we can cost this per 100 bytes. So ultimately, the proposed cost (in milligas) is:

1121 + 47 * (tokenMessageRecipient `div` 100)

The Milligas constant 1121 in practise cannot be included in defaultGasTable, which only contains Gas, not Milligas. We should round up here, so instead of 1 Gas, this is 2 Gas.

PR checklist:

Additionally, please justify why you should or should not do the following:

chessai commented 5 months ago

This needs a change in Pact.Interpreter to disable the new native until Pact 4.11, similar to other natives that are only enabled by a fork.

pact> (hyperlane-message-id 0)
<interactive>:0:0:Error: Invalid arguments, received [0] for x:object:* -> string
 at <interactive>:0:0: (hyperlane-message-id 0)
pact> (env-exec-config ['DisableVerifiers])
["DisableVerifiers"]
pact> (hyperlane-message-id 0)
<interactive>:0:0:Error: Cannot resolve hyperlane-message-id

Added