From 4af1dc8a2008a3b5bef103862a4b17b686952143 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Mon, 17 Feb 2025 17:14:06 +0200 Subject: [PATCH] Turn `analog-gmp` back into submodule (#1555) --- .github/workflows/pr-test-forge.yaml | 46 -- .gitmodules | 19 +- analog-gmp | 1 + analog-gmp/.env.example | 7 - analog-gmp/.gitignore | 20 - analog-gmp/LICENSE | 22 - analog-gmp/README.md | 53 -- analog-gmp/foundry.toml | 75 -- analog-gmp/lib/evm-interpreter | 1 - analog-gmp/lib/forge-std | 1 - analog-gmp/lib/frost-evm | 1 - analog-gmp/lib/solmate | 1 - analog-gmp/lib/universal-factory | 1 - analog-gmp/run-migration.sh | 93 --- analog-gmp/scripts/Deploy.sol | 505 ------------- analog-gmp/scripts/Migrate.sol | 217 ------ analog-gmp/src/Gateway.sol | 814 --------------------- analog-gmp/src/GatewayProxy.sol | 68 -- analog-gmp/src/NetworkID.sol | 130 ---- analog-gmp/src/Primitives.sol | 376 ---------- analog-gmp/src/interfaces/IExecutor.sol | 106 --- analog-gmp/src/interfaces/IGateway.sol | 61 -- analog-gmp/src/interfaces/IGmpReceiver.sol | 24 - analog-gmp/src/interfaces/IUpgradable.sol | 14 - analog-gmp/src/storage/Routes.sol | 286 -------- analog-gmp/src/storage/Shards.sol | 382 ---------- analog-gmp/src/utils/BranchlessMath.sol | 456 ------------ analog-gmp/src/utils/ERC1967.sol | 93 --- analog-gmp/src/utils/EnumerableSet.sol | 236 ------ analog-gmp/src/utils/Float9x56.sol | 453 ------------ analog-gmp/src/utils/GasUtils.sol | 512 ------------- analog-gmp/src/utils/Hashing.sol | 101 --- analog-gmp/src/utils/Pointer.sol | 253 ------- analog-gmp/src/utils/Schnorr.sol | 62 -- analog-gmp/test/Batching.t.sol | 505 ------------- analog-gmp/test/EnumerableSet.t.sol | 208 ------ analog-gmp/test/Example.t.sol | 168 ----- analog-gmp/test/Float9x56.t.sol | 367 ---------- analog-gmp/test/GasUtils.t.sol | 241 ------ analog-gmp/test/Gateway.t.sol | 788 -------------------- analog-gmp/test/GatewayProxy.t.sol | 123 ---- analog-gmp/test/GmpTestTools.sol | 324 -------- analog-gmp/test/GmpTestTools.t.sol | 129 ---- analog-gmp/test/MockERC20.sol | 88 --- analog-gmp/test/Random.sol | 32 - analog-gmp/test/TestUtils.sol | 542 -------------- analog-gmp/test/utils/BaseTest.sol | 125 ---- analog-gmp/test/utils/GasSpender.sol | 85 --- analog-gmp/test/utils/GasSpender.t.sol | 85 --- analog-gmp/test/utils/GmpProxy.sol | 59 -- analog-gmp/test/utils/GmpProxy.t.sol | 57 -- 51 files changed, 5 insertions(+), 9411 deletions(-) delete mode 100644 .github/workflows/pr-test-forge.yaml create mode 160000 analog-gmp delete mode 100644 analog-gmp/.env.example delete mode 100644 analog-gmp/.gitignore delete mode 100644 analog-gmp/LICENSE delete mode 100644 analog-gmp/README.md delete mode 100644 analog-gmp/foundry.toml delete mode 160000 analog-gmp/lib/evm-interpreter delete mode 160000 analog-gmp/lib/forge-std delete mode 160000 analog-gmp/lib/frost-evm delete mode 160000 analog-gmp/lib/solmate delete mode 160000 analog-gmp/lib/universal-factory delete mode 100755 analog-gmp/run-migration.sh delete mode 100644 analog-gmp/scripts/Deploy.sol delete mode 100644 analog-gmp/scripts/Migrate.sol delete mode 100644 analog-gmp/src/Gateway.sol delete mode 100644 analog-gmp/src/GatewayProxy.sol delete mode 100644 analog-gmp/src/NetworkID.sol delete mode 100644 analog-gmp/src/Primitives.sol delete mode 100644 analog-gmp/src/interfaces/IExecutor.sol delete mode 100644 analog-gmp/src/interfaces/IGateway.sol delete mode 100644 analog-gmp/src/interfaces/IGmpReceiver.sol delete mode 100644 analog-gmp/src/interfaces/IUpgradable.sol delete mode 100644 analog-gmp/src/storage/Routes.sol delete mode 100644 analog-gmp/src/storage/Shards.sol delete mode 100644 analog-gmp/src/utils/BranchlessMath.sol delete mode 100644 analog-gmp/src/utils/ERC1967.sol delete mode 100644 analog-gmp/src/utils/EnumerableSet.sol delete mode 100644 analog-gmp/src/utils/Float9x56.sol delete mode 100644 analog-gmp/src/utils/GasUtils.sol delete mode 100644 analog-gmp/src/utils/Hashing.sol delete mode 100644 analog-gmp/src/utils/Pointer.sol delete mode 100644 analog-gmp/src/utils/Schnorr.sol delete mode 100644 analog-gmp/test/Batching.t.sol delete mode 100644 analog-gmp/test/EnumerableSet.t.sol delete mode 100644 analog-gmp/test/Example.t.sol delete mode 100644 analog-gmp/test/Float9x56.t.sol delete mode 100644 analog-gmp/test/GasUtils.t.sol delete mode 100644 analog-gmp/test/Gateway.t.sol delete mode 100644 analog-gmp/test/GatewayProxy.t.sol delete mode 100644 analog-gmp/test/GmpTestTools.sol delete mode 100644 analog-gmp/test/GmpTestTools.t.sol delete mode 100644 analog-gmp/test/MockERC20.sol delete mode 100644 analog-gmp/test/Random.sol delete mode 100644 analog-gmp/test/TestUtils.sol delete mode 100644 analog-gmp/test/utils/BaseTest.sol delete mode 100644 analog-gmp/test/utils/GasSpender.sol delete mode 100644 analog-gmp/test/utils/GasSpender.t.sol delete mode 100644 analog-gmp/test/utils/GmpProxy.sol delete mode 100644 analog-gmp/test/utils/GmpProxy.t.sol diff --git a/.github/workflows/pr-test-forge.yaml b/.github/workflows/pr-test-forge.yaml deleted file mode 100644 index 1660ccd0b..000000000 --- a/.github/workflows/pr-test-forge.yaml +++ /dev/null @@ -1,46 +0,0 @@ -name: forge - -on: - push: - branches: - - master - - main - - staging - - release-v* - pull_request: - paths: - - '.github/workflows/pr-test-forge.yaml' - - 'analog-gmp/**' - - 'lib/**' -env: - FOUNDRY_PROFILE: ci - -jobs: - check: - strategy: - fail-fast: true - - name: Foundry project - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - - - name: Run Forge build - run: | - cd analog-gmp - forge --version - forge build --sizes - id: build - - - name: Run Forge tests - run: | - cd analog-gmp - forge test -vvv - id: test diff --git a/.gitmodules b/.gitmodules index 70a5f7c26..c9b7b6212 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,15 +1,4 @@ -[submodule "analog-gmp/lib/forge-std"] - path = analog-gmp/lib/forge-std - url = https://github.com/foundry-rs/forge-std -[submodule "analog-gmp/lib/frost-evm"] - path = analog-gmp/lib/frost-evm - url = https://github.com/Analog-Labs/frost-evm -[submodule "analog-gmp/lib/universal-factory"] - path = analog-gmp/lib/universal-factory - url = https://github.com/Analog-Labs/universal-factory -[submodule "analog-gmp/lib/solmate"] - path = analog-gmp/lib/solmate - url = https://github.com/transmissions11/solmate -[submodule "analog-gmp/lib/evm-interpreter"] - path = analog-gmp/lib/evm-interpreter - url = https://github.com/Analog-Labs/evm-interpreter +[submodule "analog-gmp"] + path = analog-gmp + url = git@github.com:Analog-Labs/analog-gmp.git + branch = dvc/changes diff --git a/analog-gmp b/analog-gmp new file mode 160000 index 000000000..9a881b06f --- /dev/null +++ b/analog-gmp @@ -0,0 +1 @@ +Subproject commit 9a881b06f898d0eb33a61d3d61250470fc3c91fb diff --git a/analog-gmp/.env.example b/analog-gmp/.env.example deleted file mode 100644 index c6ec7128d..000000000 --- a/analog-gmp/.env.example +++ /dev/null @@ -1,7 +0,0 @@ -PRIVATE_KEY= -PROXY_ADDRESS=0x000000033763b9d6d94efd3209dc255686aa8fba -PROXY_DEPLOYER=0x9020e86C34Da64C78fd290eBee2E171C402F6890 -#PROXY_ADDRESS=0x000000007f56768de3133034fa730a909003a165 -SEPOLIA_RPC_URL=https://ethereum-sepolia-rpc.publicnode.com -SHIBUYA_RPC_URL=https://evm.shibuya.astar.network -POLYGON_AMOY_RPC_URL=https://rpc-amoy.polygon.technology diff --git a/analog-gmp/.gitignore b/analog-gmp/.gitignore deleted file mode 100644 index ded856a92..000000000 --- a/analog-gmp/.gitignore +++ /dev/null @@ -1,20 +0,0 @@ -# Compiler files -cache/ -out/ - -# Ignores development broadcast logs -!/broadcast -/broadcast/*/31337/ -/broadcast/**/dry-run/ - -# Docs -docs/ - -# Dotenv file -.env - -# MacOS -**/.DS_Store - -# built artifacts -src/artifacts/ diff --git a/analog-gmp/LICENSE b/analog-gmp/LICENSE deleted file mode 100644 index 93d9a107a..000000000 --- a/analog-gmp/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016-2024 Zeppelin Group Ltd and contributors - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/analog-gmp/README.md b/analog-gmp/README.md deleted file mode 100644 index caf02622f..000000000 --- a/analog-gmp/README.md +++ /dev/null @@ -1,53 +0,0 @@ -## Analog's Contracts - -**This repository contains all the necessary ingredients for successful cross-chain development utilizing the Analog General Message Passing protocol.** - -## Dependencies - -This project uses **Forge** Ethereum testing framework (like Truffle, Hardhat and DappTools). -Install instructions: https://book.getfoundry.sh/ - -## Usage - -### Build - -```sh -forge build -``` - -### Test - -```sh -forge test -``` - -### Format - -```sh -forge fmt -``` - -### Gas Snapshots - -```sh -forge snapshot -``` - -## Documentation - -Use the following command to generate project documentation: - -```sh -forge doc -b -``` - -You can now read the docs from the generated [mdbook](https://github.com/rust-lang/mdBook) as follows: - -``` sh -cd docs -mdbook serve --open -``` - -## License - -Analog's Contracts is released under the [MIT License](LICENSE). diff --git a/analog-gmp/foundry.toml b/analog-gmp/foundry.toml deleted file mode 100644 index 3f1eb27e5..000000000 --- a/analog-gmp/foundry.toml +++ /dev/null @@ -1,75 +0,0 @@ -[profile.default] -src = "src" -test = "test" -out = "out" -libs = ["lib"] -# Permissions -fs_permissions = [{ access = "read", path = "./lib/universal-factory/abi" }] - -######## -# Lint # -######## -deny_warnings = true - -################ -# Solc options # -################ -solc = '0.8.28' -# Using `shanghai` once other EVM chains such as `Astar/Shibuya` and -# `Ethereum Classic` doesn't support `cancun` yet. -# - https://github.com/rust-ethereum/evm/issues/290 -# - https://ethereumclassic.org/knowledge/history -evm_version = 'shanghai' -optimizer = true -optimizer_runs = 500000 - -############### -# EVM options # -############### -gas_limit = 30000000 -gas_price = 1 -block_base_fee_per_gas = 0 -block_gas_limit = 30000000 - -##################### -# optimizer details # -##################### -[profile.default.optimizer_details] -yul = true -# The peephole optimizer is always on if no details are given, -# use details to switch it off. -peephole = true -# The inliner is always off if no details are given, -# use details to switch it on. -inliner = true -# The unused jumpdest remover is always on if no details are given, -# use details to switch it off. -jumpdest_remover = true -# Sometimes re-orders literals in commutative operations. -order_literals = true -# Removes duplicate code blocks -deduplicate = true -# Common subexpression elimination, this is the most complicated step but -# can also provide the largest gain. -cse = true -# Optimize representation of literal numbers and strings in code. -constant_optimizer = true -# Use unchecked arithmetic when incrementing the counter of for loops -# under certain circumstances. It is always on if no details are given. -simple_counter_for_loop_unchecked_increment = true - -# Fuzz tests options -[fuzz] -# Reduce the numbers of runs if fuzz tests takes too long in your machine. -runs = 2500 - -# When debuging fuzz tests, uncomment this seed to make tests reproducible. -# seed = "0xdeadbeefdeadbeefdeadbeefdeadbeef" - -# RPC endpoints -[rpc_endpoints] -sepolia = "https://ethereum-sepolia-rpc.publicnode.com" -shibuya = "https://evm.shibuya.astar.network" -amoy = "https://rpc-amoy.polygon.technology" -arbitrum_sepolia = "https://arbitrum-sepolia.gateway.tenderly.co" -bnb_testnet = "https://bsc-testnet-rpc.publicnode.com" diff --git a/analog-gmp/lib/evm-interpreter b/analog-gmp/lib/evm-interpreter deleted file mode 160000 index 75df08b2e..000000000 --- a/analog-gmp/lib/evm-interpreter +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 75df08b2e92510e2812c67fe6df79d4dd5e57806 diff --git a/analog-gmp/lib/forge-std b/analog-gmp/lib/forge-std deleted file mode 160000 index bf909b22f..000000000 --- a/analog-gmp/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bf909b22fa55e244796dfa920c9639fdffa1c545 diff --git a/analog-gmp/lib/frost-evm b/analog-gmp/lib/frost-evm deleted file mode 160000 index 8f10a6e3b..000000000 --- a/analog-gmp/lib/frost-evm +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8f10a6e3b9afaaf9523fdea2aeb1918919a7716e diff --git a/analog-gmp/lib/solmate b/analog-gmp/lib/solmate deleted file mode 160000 index c93f7716c..000000000 --- a/analog-gmp/lib/solmate +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c93f7716c9909175d45f6ef80a34a650e2d24e56 diff --git a/analog-gmp/lib/universal-factory b/analog-gmp/lib/universal-factory deleted file mode 160000 index f19228149..000000000 --- a/analog-gmp/lib/universal-factory +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f19228149ab806f5c8b859c4d65d3625130fb850 diff --git a/analog-gmp/run-migration.sh b/analog-gmp/run-migration.sh deleted file mode 100755 index 0a6256f04..000000000 --- a/analog-gmp/run-migration.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/bin/bash -set -e - -# By default, doesn't broadcast any transactions. -DRY_RUN=1 - -# Setup console colors -if test -t 1 && command -v tput >/dev/null 2>&1; then - ncolors=$(tput colors) - if test -n "${ncolors}" && test "${ncolors}" -ge 8; then - bold_color=$(tput bold) - green_color=$(tput setaf 2) - warn_color=$(tput setaf 3) - error_color=$(tput setaf 1) - reset_color=$(tput sgr0) - fi - # 72 used instead of 80 since that's the default of pr - ncols=$(tput cols) -fi -: "${ncols:=72}" - -# process arguments -while [[ $# -gt 0 ]] -do - case "$1" in - --migrate) - unset DRY_RUN - shift 1 - ;; - --proxy=*) - PROXY_ADDRESS="${i#*=}" - shift 1 - ;; - -pk=*|--private-key=*) - PRIVATE_KEY="${i#*=}" - shift 1 - ;; - --sepolia-rpc=*) - SEPOLIA_RPC_URL="${i#*=}" - shift 1 - ;; - --shibuya-rpc=*) - SHIBUYA_RPC_URL="${i#*=}" - shift 1 - ;; - --amoy-rpc=*) - POLYGON_AMOY_RPC_URL="${i#*=}" - shift 1 - ;; - *) - warn "Unknown argument: $1" - echo "Usage: $0 --pk= --proxy=PROXY_ADDRESS [--migrate] [--sepolia-rpc=] [--shibuya-rpc=] [--amoy-rpc=]" - ;; - esac -done - -# Load .env file -if [ -f .env ]; then - echo "Load .env file" - source .env -else - echo ".env file not found, run 'cp .env.example .env' and fill the values" -fi - -# Check if PRIVATE_KEY is set -if [ -z "${PRIVATE_KEY}" ]; then - echo "PRIVATE_KEY is not set" - exit 1 -fi - -# Check if PROXY_ADDRESS is set -if [ -z "${PROXY_ADDRESS}" ]; then - echo "PROXY_ADDRESS is not set" - exit 1 -fi - -# Set fork-url -PARAMS=(-vvvv) - -# Verify if the migration is going to be broadcasted -if [ -z "${DRY_RUN}" ]; then - read -r -p "running in broadcast mode, the transaction will be broadcasted, are you sure you want to continue? [y/n] " response - case "$response" in - [yY][eE][sS]|[yY]) - PARAMS+=(--broadcast) - ;; - *) - echo "running in dry-mode..." - ;; - esac -fi - -forge script ./scripts/Migrate.sol "${PARAMS[@]}" diff --git a/analog-gmp/scripts/Deploy.sol b/analog-gmp/scripts/Deploy.sol deleted file mode 100644 index 94d6c3e8f..000000000 --- a/analog-gmp/scripts/Deploy.sol +++ /dev/null @@ -1,505 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (scripts/Deploy.sol) - -pragma solidity ^0.8.0; - -import {FactoryUtils} from "@universal-factory/FactoryUtils.sol"; -import {IUniversalFactory} from "@universal-factory/IUniversalFactory.sol"; -import {Script} from "forge-std/Script.sol"; -import {console} from "forge-std/console.sol"; -import {IGateway} from "../src/interfaces/IGateway.sol"; -import {NetworkID, NetworkIDHelpers} from "../src/NetworkID.sol"; -import {ERC1967} from "../src/utils/ERC1967.sol"; -import {BranchlessMath} from "../src/utils/BranchlessMath.sol"; -import {UFloat9x56, UFloatMath} from "../src/utils/Float9x56.sol"; -import {GatewayProxy} from "../src/GatewayProxy.sol"; -import {Gateway} from "../src/Gateway.sol"; -import { - TssKey, - GmpMessage, - UpdateKeysMessage, - Signature, - Network, - GmpStatus, - GmpSender, - PrimitiveUtils -} from "../src/Primitives.sol"; - -/** - * @dev Message payload used to update the network info. - * @param networkId Domain EIP-712 - Replay Protection Mechanism. - * @param domainSeparator Domain EIP-712 - Replay Protection Mechanism. - * @param gasLimit The maximum amount of gas we allow on this particular network. - * @param relativeGasPrice Gas price of destination chain, in terms of the source chain token. - * @param baseFee Base fee for cross-chain message approval on destination, in terms of source native gas token. - * @param mortality maximum block in which this message is valid. - */ -struct UpdateNetworkInfo { - uint16 networkId; - bytes32 domainSeparator; - uint64 gasLimit; - UFloat9x56 relativeGasPrice; - uint128 baseFee; - uint64 mortality; -} - -contract MigrateGateway is Script { - using NetworkIDHelpers for NetworkID; - using FactoryUtils for IUniversalFactory; - - /** - * @dev The codehash of the proxy contract - */ - bytes32 internal constant PROXY_CODEHASH = 0x54afeb06256bce71659256132ac18f1515de3011aaec4fbd6fc7b0c00c7263d8; - - /** - * @dev The minimal balance required to deploy the proxy contract - */ - uint256 internal constant MINIMAL_DEPLOYER_BALANCE = 0.5 ether; - - /** - * @dev Universal Factory used to deploy the implementation contract - * see https://github.com/Analog-Labs/universal-factory/tree/main for mode details. - */ - IUniversalFactory internal constant FACTORY = IUniversalFactory(0x0000000000001C4Bf962dF86e38F0c10c7972C6E); - - /** - * @dev Bytecode hash of the Universal Factory, used to verify if the contract is deployed. - */ - bytes32 internal constant FACTORY_CODEHASH = 0x0dac89b851eaa2369ef725788f1aa9e2094bc7819f5951e3eeaa28420f202b50; - - /** - * @dev Hash of the implementation contract creation code. - */ - bytes32 internal constant IMPLEMENTATION_CODEHASH = keccak256(type(Gateway).creationCode); - - /** - * @dev Salt of the implementation. - */ - bytes32 internal constant IMPLEMENTATION_SALT = bytes32(uint256(0x010000000000)); - - /** - * @dev Default Proxy Admin, if none is provided, use this one. - */ - address internal constant DEFAULT_ADMIN_ACCOUNT = 0xB41440FF80e1083350c91B21DE1061e0920A75AD; - - /** - * Information about the current state of the migration - * @param forkId The network fork id, see: https://book.getfoundry.sh/forge/fork-testing#forking-cheatcodes - * @param mortality The maximum block number where the migration can be executed. - * @param proxyAddress The address of the proxy contract - */ - struct State { - uint256 forkId; - uint64 mortality; - address proxyAddress; - } - - struct NetworkConfiguration { - string name; - uint256 forkID; - uint256 chainID; - bool hasProxy; - address adminAddress; - address implementationContract; - UpdateNetworkInfo info; - } - - /** - * Information about the current state of the migration - * @param proxy The address of the proxy contract - * @param proxyAdmin The address of the proxy admin - * @param proxyDeployer The address of the proxy deployer - * @param proxyDeployerNonce The nonce that must be used by the deployer to deploy the proxy contract - * @param implementationDeployer Account used to deploy the implementation contract. - */ - struct Configuration { - address proxy; - address proxyAdmin; - address proxyDeployer; - uint256 proxyDeployerNonce; - address implementationDeployer; - NetworkConfiguration[] networks; - } - - /** - * @dev Maps the network id to its migration state - */ - mapping(uint16 => State) public states; - - // Computes the EIP-712 domain separador - function _computeDomainSeparator(uint256 networkId, address addr) private pure returns (bytes32) { - return keccak256( - abi.encode( - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), - keccak256("Analog Gateway Contract"), - keccak256("0.1.0"), - uint256(networkId), - address(addr) - ) - ); - } - - function _toString(string memory a, uint256 b, string memory c, uint256 d) private pure returns (string memory) { - return string(bytes.concat( - bytes(a), - bytes(vm.toString(b)), - bytes(c), - bytes(vm.toString(d)) - )); - } - - function _toString(string memory a, address b, string memory c, address d) private pure returns (string memory) { - return string(bytes.concat( - bytes(a), - bytes(vm.toString(b)), - bytes(c), - bytes(vm.toString(d)) - )); - } - - /** - * @dev Convert an address to string. - */ - function _toString(string memory a, address b, string memory c) private pure returns (string memory) { - return string(bytes.concat(bytes(a), bytes(vm.toString(b)), bytes(c))); - } - - /** - * @dev Find what nonce the deployer must use to deploy the contract at the specified address. - */ - function _findDeployerNonce(address deployer, address contractAddress) private pure returns (uint64) { - for (uint256 nonce = 0; nonce < 1000; nonce++) { - // Obs: using this instead of `vm.computeCreateAddress(deployer, nonce);` to avoid `OutOfMemory` error. - address addr = FACTORY.computeCreateAddress(deployer, nonce); - if (contractAddress == addr) { - return uint64(nonce); - } - } - return type(uint64).max; - } - - /** - * @dev Retrieve network info and verify if the deployer is the admin of the proxy contract. - */ - function _setupNetwork(NetworkConfiguration memory network, address proxyAddress, address proxyDeployer, address proxyAdmin) - private - view - { - network.chainID = block.chainid; - - // Check if the chain has a network id - NetworkID networkId; - { - bool exists; - (exists,networkId) = NetworkIDHelpers.tryFromChainID(block.chainid); - require(exists, string(bytes.concat(bytes("network id not found for chain "), bytes(vm.toString(block.chainid))))); - } - - ////////////////////////////////////////////////// - // Verify if the proxy is deployed and is valid // - ////////////////////////////////////////////////// - network.hasProxy = proxyAddress.code.length > 0; - if (network.hasProxy) { - require(proxyAddress.codehash == PROXY_CODEHASH, "invalid proxy codehash"); - } else { - console.log("PROXY NOT DEPLOYED"); - } - - //////////////////////////////////////////////// - // Verify if the UNIVERSAL FACORY is deployed // - //////////////////////////////////////////////// - require(address(FACTORY).code.length > 0, "universal factory not deployed"); - require(address(FACTORY).codehash == FACTORY_CODEHASH, "invalid universal factory codehash"); - - ///////////////////////// - // Retrieve Chain Info // - ///////////////////////// - - // Allocate the network information - UpdateNetworkInfo memory info = network.info; - info.networkId = networkId.asUint(); - info.domainSeparator = bytes32(0); - info.gasLimit = 0; - info.relativeGasPrice = UFloatMath.ONE; - info.baseFee = 0; - info.mortality = 0; - console.log(" NETWORK ID", info.networkId); - - // Print information about the proxy contract - if (network.hasProxy) { - // Check if the gateway.networkId == info.networkId - uint16 gatewayNetworkId = IGateway(proxyAddress).networkId(); - vm.assertEq(gatewayNetworkId, info.networkId, "network id mismatch"); - - // Retrieve the proxy admin - network.adminAddress = address(uint160(uint256(vm.load(proxyAddress, ERC1967.ADMIN_SLOT)))); - console.log(" PROXY_ADMIN", network.adminAddress); - - // Retrieve the current implementation - network.implementationContract = address(uint160(uint256(vm.load(proxyAddress, ERC1967.IMPLEMENTATION_SLOT)))); - console.log(" IMPLEMENATTION", network.implementationContract); - } else { - console.log(" PROXY_ADMIN", "N/A"); - console.log(" IMPLEMENATTION", "N/A"); - network.adminAddress = address(0); - network.implementationContract = address(0); - } - - // Print information about the current network - uint256 nonce = vm.getNonce(proxyDeployer); - console.log(" GATEWAY BALANCE", proxyAddress.balance); - console.log(" DEPLOYER BALANCE", proxyDeployer.balance); - console.log(" DEPLOYER NONCE", nonce); - console.log(" ADMIN BALANCE", proxyAdmin.balance); - console.log(" ADMIN NONCE", vm.getNonce(proxyAdmin)); - console.log(" LATEST BLOCK", block.number); - console.log(" BLOCK GAS LIMIT", block.gaslimit); - console.log(" CHAIN ID", block.chainid); - console.log(" GAS PRICE", tx.gasprice); - console.log(" BASE FEE", block.basefee, "\n"); - - if (network.hasProxy && network.adminAddress != proxyAdmin) { - revert(_toString("proxy admin mismatch, got ", network.adminAddress, " but expected ", proxyAdmin)); - } - require(block.gaslimit < uint64(type(int64).max), "block gas limit exceeds the limit of int64"); - require(block.gaslimit > 1_000_000, "block gas limit is too low"); - require(block.number < uint64(type(int64).max), "block number limit exceeds the limit of int64"); - require(block.number > 1_000_000, "block number is low, is this a local testnet?"); - - // If this chain needs proxy, the deployer nonce must match the expected nonce. - if (network.hasProxy == false) { - address addr = vm.computeCreateAddress(proxyDeployer, nonce); - if (addr != proxyAddress) { - uint256 expected = _findDeployerNonce(proxyDeployer, proxyAddress); - revert(_toString("Deployer nonce mismatch, got ", nonce, " but expected ", expected)); - } - } - - // Update network information - info.domainSeparator = _computeDomainSeparator(info.networkId, proxyAddress); - info.gasLimit = uint64(block.gaslimit >> 1); - info.relativeGasPrice = UFloatMath.ONE; - info.baseFee = 0; - info.mortality = uint64(block.number + 128); - } - - function _setupNetworks(address proxyAddress, address proxyDeployer, address proxyAdmin) private returns (NetworkConfiguration[] memory networks) { - string[2][] memory urls = vm.rpcUrls(); - require(urls.length > 0, "no rpc urls found, check the `foundry.toml` file"); - networks = new NetworkConfiguration[](urls.length); - - // Initialize and check the network information - for (uint256 i = 0; i < urls.length; i++) { - networks[i] = NetworkConfiguration({ - name: urls[i][0], - forkID: 0, - chainID: 0, - hasProxy: false, - adminAddress: address(0), - implementationContract: address(0), - info: UpdateNetworkInfo({ - networkId: 0, - domainSeparator: bytes32(0), - gasLimit: 0, - relativeGasPrice: UFloatMath.ONE, - baseFee: 0, - mortality: 0 - }) - }); - NetworkConfiguration memory network = networks[i]; - string[2] memory entry = urls[i]; - console.log(" BLOCKCHAIN", entry[0]); - console.log(" RPC URL", entry[1]); - network.forkID = vm.createSelectFork(entry[1]); - _setupNetwork(network, proxyAddress, proxyDeployer, proxyAdmin); - console.log(""); - } - } - - /** - * @dev Script entry point, the following core will upgrade the gateway contract of all networks. - */ - function run() external { - Configuration memory config; - { - // Retrieve the `GatewayProxy` address - address proxy = vm.envOr("PROXY_ADDRESS", address(0)); - if (proxy == address(0)) { - proxy = vm.promptAddress("Enter the address of the proxy contract"); - } - - // Retrieve the account that must be used to deploy the proxy contract. - address proxyDeployer = vm.envOr("PROXY_DEPLOYER", address(0)); - if (proxyDeployer == address(0)) { - proxyDeployer = vm.promptAddress("Enter the address of the account that must be used to deploy the proxy contract"); - } - require(msg.sender != proxyDeployer, "The account used to deploy the implementation and the proxy cannot be the same"); - - // Find the nonce that must be used by the deployer to deploy the proxy contract - uint64 proxyDeployerNonce = _findDeployerNonce(proxyDeployer, proxy); - if (proxyDeployerNonce == type(uint64).max) { - revert(_toString("The provided deployer ", proxyDeployer, " cannot deploy the proxy contract")); - } - - // Retrieve the proxy admin account - address proxyAdmin = vm.envOr("PROXY_ADMIN", DEFAULT_ADMIN_ACCOUNT); - - // Initialize Networks - config = Configuration({ - proxy: proxy, - proxyAdmin: proxyAdmin, - proxyDeployer: proxyDeployer, - proxyDeployerNonce: proxyDeployerNonce, - implementationDeployer: msg.sender, - networks: _setupNetworks(proxy, proxyDeployer, proxyAdmin) - }); - } - - console.log(" FUNDING ACCOUNT", config.implementationDeployer); - console.log(" PROXY_ADDRESS", config.proxy); - console.log(" PROXY DEPLOYER", config.proxyDeployer); - console.log(" ADMIN ACCOUNT", config.proxyAdmin, "\n"); - - // Iterate over all the RPC URLs, defined in the `foundry.toml` file - NetworkConfiguration[] memory allNetworks = config.networks; - - // Filter the networks that need a proxy - NetworkConfiguration[] memory needsProxy = new NetworkConfiguration[](allNetworks.length); - { - uint256 count = 0; - for (uint256 i=0; i MINIMAL_DEPLOYER_BALANCE, "insufficient funds"); - - vm.startBroadcast(msg.sender); - payable(deployer).transfer(MINIMAL_DEPLOYER_BALANCE - balance); - vm.stopBroadcast(); - } - console.log(""); - } - - console.log(" --------------- DEPLOYING IMPLEMENTATION --------------- "); - { - bytes memory bytecode = type(Gateway).creationCode; - console.log(" IMPL HASH", vm.toString(bytes32(keccak256(bytecode))), "\n"); - for (uint256 i = 0; i < needsProxy.length; i++) { - NetworkConfiguration memory network = needsProxy[i]; - - // Switch to the selected network - vm.selectFork(network.forkID); - require(network.chainID == block.chainid, "chain id mismatch"); - - // Print information about the current network - console.log(" -- BLOCKCHAIN", network.name); - console.log(" CHAIN ID", block.chainid); - console.log(" NETWORK ID", network.info.networkId); - console.log(" BLOCK NUMBER", block.number); - - // Check if the the implementation is already deployed. - bytes memory initCode = bytes.concat( - bytecode, - abi.encode(uint16(network.info.networkId), address(config.proxy)) - ); - address deployer = config.implementationDeployer; - network.implementationContract = FACTORY.computeCreate2Address(IMPLEMENTATION_SALT, initCode); - console.log(" DEPLOYER", deployer); - console.log(" DEPLOYER BALANCE", deployer.balance); - console.log(" CONTRACT ADDRESS", network.implementationContract); - - if (network.implementationContract.code.length == 0) { - // Deploy the implementation contract - vm.startBroadcast(deployer); - address implementation = FACTORY.create2(IMPLEMENTATION_SALT, initCode); - vm.stopBroadcast(); - vm.assertEq(network.implementationContract, implementation, "implementation address mismatch"); - console.log(" DEPLOYMENT STATUS", "Deployed"); - } else { - console.log(" DEPLOYMENT STATUS", "Skipped, already deployed"); - } - console.log(); - } - } - - console.log(" -------------------- DEPLOYING PROXY ------------------- "); - for (uint256 i = 0; i < needsProxy.length; i++) { - NetworkConfiguration memory network = needsProxy[i]; - - // Switch to the selected network - vm.selectFork(network.forkID); - require(network.chainID == block.chainid, "chain id mismatch"); - - console.log(" -- BLOCKCHAIN", network.name); - console.log(" CHAIN ID", block.chainid); - console.log(" NETWORK ID", network.info.networkId); - console.log(" BLOCK NUMBER", block.number); - - address deployer = config.proxyDeployer; - address implementation = network.implementationContract; - uint256 nonce = vm.getNonce(deployer); - console.log(" DEPLOYER ACCOUNT", deployer); - console.log(" DEPLOYER BALANCE", deployer.balance); - console.log(" DEPLOYER NONCE", nonce); - console.log(" IMPLEMENTATION", implementation); - console.log(" PROXY ADDRESS", config.proxy); - require(config.proxy.code.length == 0, "proxy already deployed"); - require(deployer.balance >= MINIMAL_DEPLOYER_BALANCE, "deployer insufficient funds"); - require(nonce == config.proxyDeployerNonce, "wrong deployer nonce"); - require(implementation.code.length > 0, "implementation not found"); - - // TODO: Load the shards from the network, currently only the admin can add the shards. - TssKey[] memory emptyShards = new TssKey[](0); - Network[] memory emptyNetworks = new Network[](0); - bytes memory initializer = abi.encodeCall(Gateway.initialize, (config.proxyAdmin, emptyShards, emptyNetworks)); - - vm.startBroadcast(deployer); - // address deployed = address(new GatewayProxy(implementation, initializer)); - address deployed = address(new GatewayProxy(config.proxyAdmin)); // TODO: fix me - vm.stopBroadcast(); - console.log(" PROXY ADDRESS", deployed); - console.log(" DEPLOYMENT STATUS", deployed == config.proxy ? "Success" : "Address Mismatch"); - console.log(""); - } - } -} diff --git a/analog-gmp/scripts/Migrate.sol b/analog-gmp/scripts/Migrate.sol deleted file mode 100644 index 4c0716c98..000000000 --- a/analog-gmp/scripts/Migrate.sol +++ /dev/null @@ -1,217 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (scripts/Upgrade.sol) - -pragma solidity ^0.8.0; - -import {Script} from "forge-std/Script.sol"; -import {console} from "forge-std/console.sol"; -import {IGateway} from "../src/interfaces/IGateway.sol"; -import {ERC1967} from "../src/utils/ERC1967.sol"; -import {UFloat9x56, UFloatMath} from "../src/utils/Float9x56.sol"; -import {Gateway} from "../src/Gateway.sol"; -import { - TssKey, - GmpMessage, - UpdateKeysMessage, - UpdateNetworkInfo, - Signature, - Network, - GmpStatus, - GmpSender, - PrimitiveUtils -} from "../src/Primitives.sol"; - -contract MigrateGateway is Script { - bytes32 internal constant PROXY_CODEHASH = 0x54afeb06256bce71659256132ac18f1515de3011aaec4fbd6fc7b0c00c7263d8; - - /** - * Information about the current state of the migration - * @param forkId The network fork id, see: https://book.getfoundry.sh/forge/fork-testing#forking-cheatcodes - * @param mortality The maximum block number where the migration can be executed. - * @param proxyAddress The address of the proxy contract - */ - struct State { - uint256 forkId; - uint64 mortality; - address proxyAddress; - } - - /** - * @dev Maps the network id to its migration state - */ - mapping(uint16 => State) public states; - - // Computes the EIP-712 domain separador - function _computeDomainSeparator(uint256 networkId, address addr) private pure returns (bytes32) { - return keccak256( - abi.encode( - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), - keccak256("Analog Gateway Contract"), - keccak256("0.1.0"), - uint256(networkId), - address(addr) - ) - ); - } - - /** - * @dev Retrieve network info and verify if the deployer is the admin of the proxy contract. - */ - function _setupNetwork(string memory name, address proxyAddress, address deployer) - private - returns (UpdateNetworkInfo memory info) - { - console.log(string(bytes.concat(" -- CHECKING ", bytes(name)))); - - // Retrieve the RPC URL - string memory rpcUrl = vm.envOr(name, string("")); - require(bytes(rpcUrl).length > 0, "rpc url not found"); - console.log(" RPC", rpcUrl); - - // Create a new fork - uint256 forkId = vm.createSelectFork(rpcUrl); - - // Verify if the provided proxy address is valid - { - require(proxyAddress.code.length > 0, "UpgradeGateway: proxy doesn't exists"); - bytes32 codehash; - assembly { - codehash := extcodehash(proxyAddress) - } - require(codehash == PROXY_CODEHASH, "UpgradeGateway: invalid proxy codehash"); - } - - // Allocate the network information - info = UpdateNetworkInfo({ - networkId: 0, - domainSeparator: bytes32(0), - gasLimit: 0, - relativeGasPrice: UFloatMath.ONE, - baseFee: 0, - mortality: 0 - }); - - // Retrieve the network id - info.networkId = IGateway(proxyAddress).networkId(); - console.log(" NETWORK ID", info.networkId); - - // Retrieve the proxy admin - address admin = address(uint160(uint256(vm.load(proxyAddress, ERC1967.ADMIN_SLOT)))); - console.log(" PROXY_ADMIN", admin); - - // Retrieve the current implementation - address implementation = address(uint160(uint256(vm.load(proxyAddress, ERC1967.IMPLEMENTATION_SLOT)))); - console.log(" IMPLEMENATTION", implementation); - - // Print information about the current network - console.log(" GATEWAY BALANCE", proxyAddress.balance); - console.log(" DEPLOYER BALANCE", deployer.balance); - console.log(" LATEST BLOCK", block.number); - console.log(" BLOCK GAS LIMIT", block.gaslimit); - console.log(" CHAIN ID", block.chainid); - console.log(" GAS PRICE", tx.gasprice); - console.log(" BASE FEE", block.basefee, "\n"); - - require(admin == deployer, "deployer is not the admin if this contract"); - require(block.gaslimit < uint64(type(int64).max), "block gas limit exceeds the limit of int64"); - require(block.gaslimit > 1_000_000, "block gas limit is too low"); - require(block.number < uint64(type(int64).max), "block number limit exceeds the limit of int64"); - require(block.number > 1_000_000, "block number is low, is this a local testnet?"); - - // Update network information - info.domainSeparator = _computeDomainSeparator(info.networkId, proxyAddress); - info.gasLimit = uint64(block.gaslimit >> 1); - info.relativeGasPrice = UFloatMath.ONE; - info.baseFee = 0; - info.mortality = uint64(block.number + 128); - - // Save migration state information - states[info.networkId] = State({forkId: forkId, mortality: info.mortality, proxyAddress: proxyAddress}); - } - - /** - * @dev Verify the networks and check if the deployer is the admin of the proxy contract - */ - function _setupNetworks(address proxyAddress, address deployer) - private - returns (UpdateNetworkInfo[] memory networks) - { - networks = new UpdateNetworkInfo[](3); - networks[0] = _setupNetwork("SEPOLIA_RPC_URL", proxyAddress, deployer); - - networks[1] = _setupNetwork("SHIBUYA_RPC_URL", proxyAddress, deployer); - require(networks[0].networkId != networks[1].networkId, "SEPOLIA and SHIBUYA have the same network id"); - - networks[2] = _setupNetwork("POLYGON_AMOY_RPC_URL", proxyAddress, deployer); - require(networks[2].networkId != networks[0].networkId, "AMOY and SEPOLIA have the same network id"); - require(networks[2].networkId != networks[0].networkId, "AMOY and SHIBUYA have the same network id"); - } - - /** - * @dev Deploy the new Gateway implementation and upgrade the proxy contract - */ - function _upgradeNetwork(uint16 networkId, uint256 deployerPrivateKey, UpdateNetworkInfo[] memory networks) - private - { - State memory state = states[networkId]; - - // Switch to the fork - vm.selectFork(state.forkId); - - // Check if the implementation expected address - { - // Retrieve the deployer nonce - address deployer = vm.addr(deployerPrivateKey); - uint256 nonce = vm.getNonce(deployer); - address implementation = vm.computeCreateAddress(deployer, nonce); - console.log(" NEW IMPLEMENATTION", implementation); - } - - // Update message mortality - for (uint256 i = 0; i < networks.length; i++) { - networks[i].mortality = state.mortality; - } - - // Deploy the new implementation contract - vm.startBroadcast(deployerPrivateKey); - Gateway newImplementation = new Gateway(networkId, state.proxyAddress); - console.log(" DEPLOYED", address(newImplementation)); - - bytes memory initializer = abi.encodeCall(Gateway.updateNetworks, (networks)); - console.log(" INITIALIZER:"); - console.logBytes(initializer); - Gateway(state.proxyAddress).upgradeAndCall(address(newImplementation), initializer); - console.log(" GATEWAY UPGRADED"); - vm.stopBroadcast(); - } - - /** - * @dev Script entry point, the following core will upgrade the gateway contract of all networks. - */ - function run() external { - // Retrieve the gateway proxy address - address proxyAddress = vm.envAddress("PROXY_ADDRESS"); - console.log(" PROXY_ADDRESS", proxyAddress); - - // Retrieve deployer private key - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - address deployer = vm.addr(deployerPrivateKey); - console.log(" DEPLOYER", deployer, "\n"); - - // Setup the networks - UpdateNetworkInfo[] memory networkInfos = _setupNetworks(proxyAddress, deployer); - - // Extract the network ids - uint16[] memory networkByID = new uint16[](networkInfos.length); - for (uint256 i = 0; i < networkInfos.length; i++) { - networkByID[i] = networkInfos[i].networkId; - } - - // Upgrade the networks - for (uint256 i = 0; i < networkByID.length; i++) { - uint16 networkID = networkByID[i]; - console.log(" -- UPGRADING NETWORK", networkID); - _upgradeNetwork(networkID, deployerPrivateKey, networkInfos); - } - } -} diff --git a/analog-gmp/src/Gateway.sol b/analog-gmp/src/Gateway.sol deleted file mode 100644 index 9f0c2c944..000000000 --- a/analog-gmp/src/Gateway.sol +++ /dev/null @@ -1,814 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (src/Gateway.sol) - -pragma solidity >=0.8.0; - -import {Hashing} from "./utils/Hashing.sol"; -import {Schnorr} from "./utils/Schnorr.sol"; -import {BranchlessMath} from "./utils/BranchlessMath.sol"; -import {GasUtils} from "./utils/GasUtils.sol"; -import {ERC1967} from "./utils/ERC1967.sol"; -import {UFloat9x56, UFloatMath} from "./utils/Float9x56.sol"; -import {RouteStore} from "./storage/Routes.sol"; -import {ShardStore} from "./storage/Shards.sol"; -import {IGateway} from "./interfaces/IGateway.sol"; -import {IUpgradable} from "./interfaces/IUpgradable.sol"; -import {IGmpReceiver} from "./interfaces/IGmpReceiver.sol"; -import {IExecutor} from "./interfaces/IExecutor.sol"; -import { - Command, - InboundMessage, - GatewayOp, - GmpCallback, - GmpMessage, - GmpStatus, - GmpSender, - Network, - Route, - PrimitiveUtils, - UpdateKeysMessage, - Signature, - TssKey, - MAX_PAYLOAD_SIZE -} from "./Primitives.sol"; -import {NetworkID, NetworkIDHelpers} from "./NetworkID.sol"; - -abstract contract GatewayEIP712 { - using NetworkIDHelpers for NetworkID; - - // EIP-712: Typed structured data hashing and signing - // https://eips.ethereum.org/EIPS/eip-712 - uint16 internal immutable NETWORK_ID; - address internal immutable PROXY_ADDRESS; - - constructor(uint16 networkId, address gateway) { - NETWORK_ID = networkId; - PROXY_ADDRESS = gateway; - } -} - -contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 { - using PrimitiveUtils for UpdateKeysMessage; - using PrimitiveUtils for GmpMessage; - using PrimitiveUtils for address; - using BranchlessMath for uint256; - using UFloatMath for UFloat9x56; - using ShardStore for ShardStore.MainStorage; - using RouteStore for RouteStore.MainStorage; - using RouteStore for RouteStore.NetworkInfo; - using NetworkIDHelpers for NetworkID; - - /** - * @dev Selector of `GmpCreated` event. - * keccak256("GmpCreated(bytes32,bytes32,address,uint16,uint64,uint64,uint64,bytes)"); - */ - bytes32 private constant GMP_CREATED_EVENT_SELECTOR = - 0x081a0b65828c1720ce022ffb992d4a5ec86e2abc4c383acd4029ba8486e41b4f; - - /** - * @dev The address of the `UniversalFactory` contract, must be the same on all networks. - */ - address internal constant FACTORY = 0x0000000000001C4Bf962dF86e38F0c10c7972C6E; - - // GMP message status - mapping(bytes32 => GmpInfo) private _messages; - - // Hash of the previous GMP message submitted. - mapping(address => uint256) private _nonces; - - // Replay protection mechanism, stores the hash of the executed messages - // messageHash => shardId - mapping(bytes32 => bytes32) private _executedMessages; - - /** - * @dev GMP info stored in the Gateway Contract - * OBS: the order of the attributes matters! ethereum storage is 256bit aligned, try to keep - * the attributes 256 bit aligned, ex: nonce, block and status can be read in one storage access. - * reference: https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html - */ - struct GmpInfo { - GmpStatus status; - uint64 blockNumber; // block in which the message was processed - } - - constructor(uint16 network, address proxy) payable GatewayEIP712(network, proxy) {} - - // EIP-712 typed hash - function initialize(address proxyAdmin, TssKey[] calldata keys, Network[] calldata networks) external { - require(PROXY_ADDRESS == address(this) || msg.sender == FACTORY, "only proxy can be initialize"); - ERC1967.setAdmin(proxyAdmin); - - // Register networks - RouteStore.getMainStorage().initialize(networks, NetworkID.wrap(NETWORK_ID)); - - // Register keys - ShardStore.getMainStorage().registerTssKeys(keys); - - // emit event - emit ShardsRegistered(keys); - } - - function nonceOf(address account) external view returns (uint64) { - return uint64(_nonces[account]); - } - - function gmpInfo(bytes32 id) external view returns (GmpInfo memory) { - return _messages[id]; - } - - function keyInfo(bytes32 id) external view returns (ShardStore.ShardInfo memory) { - ShardStore.MainStorage storage store = ShardStore.getMainStorage(); - return store.get(ShardStore.ShardID.wrap(id)); - } - - function networkId() external view returns (uint16) { - return NETWORK_ID; - } - - function networkInfo(uint16 id) external view returns (RouteStore.NetworkInfo memory) { - return RouteStore.getMainStorage().get(NetworkID.wrap(id)); - } - - /** - * @dev Verify if shard exists, if the TSS signature is valid then increment shard's nonce. - */ - function _verifySignature(Signature calldata signature, bytes32 message) private view { - // Load shard from storage - ShardStore.ShardInfo storage signer = ShardStore.getMainStorage().get(signature); - - // Load y parity bit, it must be 27 (even), or 28 (odd) - // ref: https://ethereum.github.io/yellowpaper/paper.pdf - uint8 yParity = BranchlessMath.ternaryU8(signer.yParity > 0, 28, 27); - - // Verify Signature - require( - Schnorr.verify(yParity, signature.xCoord, uint256(message), signature.e, signature.s), - "invalid tss signature" - ); - } - - // Register/Revoke TSS keys using shard TSS signature - function updateKeys(Signature calldata signature, UpdateKeysMessage calldata message) external { - // Check if the message was already executed to prevent replay attacks - bytes32 messageHash = message.eip712hash(); - require(_executedMessages[messageHash] == bytes32(0), "message already executed"); - - // Verify the signature and store the message hash - _verifySignature(signature, messageHash); - _executedMessages[messageHash] = bytes32(signature.xCoord); - - // Register/Revoke shards pubkeys - ShardStore.MainStorage storage store = ShardStore.getMainStorage(); - - // Revoke tss keys (revoked keys can be registred again keeping the previous nonce) - store.revokeKeys(message.revoke); - - // Register or activate revoked keys - store.registerTssKeys(message.register); - - // Emit event - if (message.revoke.length > 0) { - emit ShardsUnregistered(message.revoke); - } - - if (message.register.length > 0) { - emit ShardsRegistered(message.register); - } - } - - /*////////////////////////////////////////////////////////////// - GATEWAY OPERATIONS AND COMMANDS - //////////////////////////////////////////////////////////////*/ - - /** - * @dev Lookup table to find the function pointer for a given command. - * Different than nested if-else, this has constant gas cost regardless the number of commands. - * - * Obs: supports up to 16 commands. - * See: `_buildCommandsLUT` and `_cmdTableLookup` methods for more details. - */ - type CommandsLookUpTable is uint256; - - /** - * @dev Dispatch a single GMP message. - */ - function _gmpCommand(bytes calldata params) private returns (bytes32 operationHash) { - require(params.length >= 256, "invalid GmpMessage"); - GmpMessage calldata gmp; - assembly { - gmp := add(params.offset, 0x20) - } - _checkGmpMessage(gmp); - // Convert the `GmpMessage` into `GmpCallback`, which is a more efficient representation. - // see `src/Primitives.sol` for more details. - GmpCallback memory callback = gmp.intoCallback(); - operationHash = callback.eip712hash; - _execute(callback); - } - - /** - * @dev Register a single shard and returns the GatewayOp hash. - */ - function _registerShardCommand(bytes calldata params) private returns (bytes32 operationHash) { - require(params.length == 64, "invalid TssKey"); - TssKey calldata newShard; - assembly { - newShard := params.offset - } - operationHash = Hashing.hash(newShard.yParity, newShard.xCoord); - _setShard(newShard); - } - - /** - * @dev Removes a single shard from the set. - */ - function _unregisterShardCommand(bytes calldata params) private returns (bytes32 operationHash) { - require(params.length == 64, "invalid TssKey"); - TssKey calldata shard; - assembly { - shard := params.offset - } - operationHash = Hashing.hash(shard.yParity, shard.xCoord); - _revokeShard(shard); - } - - /** - * Cast the command function into a uint256. - */ - function fnToPtr(function(bytes calldata) internal returns (bytes32) fn) private pure returns (uint256 ptr) { - assembly { - ptr := fn - } - } - - /** - * @dev Creates a lookup table to find the function pointer for a given command. - * - * Motivation: More efficient than nested if-else, and also guarantees a constant gas overhead for any command, which - * makes easier to estimate the gas cost necessary to execute the whole batch. - */ - function _buildCommandsLUT() private pure returns (CommandsLookUpTable) { - uint256 lookupTable; - // GMP - lookupTable = fnToPtr(_gmpCommand) << (uint256(Command.GMP) << 4); - // RegisterShard - lookupTable |= fnToPtr(_registerShardCommand) << (uint256(Command.RegisterShard) << 4); - // UnregisterShard - lookupTable |= fnToPtr(_unregisterShardCommand) << (uint256(Command.UnregisterShard) << 4); - return CommandsLookUpTable.wrap(lookupTable); - } - - /** - * @dev Get in constant gas the function pointer for the provided command. - * See `_buildCommandsLUT` for more details. - */ - function _cmdTableLookup(CommandsLookUpTable lut, Command command) - private - pure - returns (function(bytes calldata) internal returns (bytes32) fn) - { - unchecked { - // Extract the function pointer from the table using the `Command` as index. - uint256 ptr = CommandsLookUpTable.unwrap(lut) >> (uint256(command) << 4); - ptr &= 0xffff; - - // Make sure the function pointer is within the code bounds. - uint256 codeSize; - assembly { - codeSize := codesize() - } - require(ptr > 0 && ptr < codeSize, "invalid command"); - - // Converts the `uint256` back to `function(bytes calldata) internal returns (bytes32) fn` - assembly { - fn := ptr - } - } - } - - /** - * @dev Execute a batch of `GatewayOp` and returns the maximum amount of memory used in bytes and the operations root hash. - * - * This method also reuses the same memory space for each command, to prevent the memory to grow and - * increase the cost exponentially. - * @return (uint256, bytes32) Returns a tuple containing the maximum amount of memory used in bytes and the operations root hash. - */ - function _executeCommands(GatewayOp[] calldata operations) private returns (uint256, bytes32) { - // Track the free memory pointer, to reset the memory after each command executed. - uint256 freeMemPointer = GasUtils.readAllocatedMemory(); - uint256 maxAllocatedMemory = freeMemPointer; - - // Create the Command LookUp Table - CommandsLookUpTable lut = _buildCommandsLUT(); - - bytes32 operationsRootHash = bytes32(0); - for (uint256 i = 0; i < operations.length; i++) { - GatewayOp calldata operation = operations[i]; - - // Lookup the command function pointer - function(bytes calldata) internal returns (bytes32) commandFN = _cmdTableLookup(lut, operation.command); - - // Execute the command - bytes32 operationHash = commandFN(operation.params); - - // Update the operations root hash - operationsRootHash = - Hashing.hash(uint256(operationsRootHash), uint256(operation.command), uint256(operationHash)); - - // Restore the memory, to prevent the memory expansion costs to increase exponentially. - uint256 newFreeMemPointer = GasUtils.unsafeReplaceAllocatedMemory(freeMemPointer); - - // Update the Max Allocated Memory - maxAllocatedMemory = maxAllocatedMemory.max(newFreeMemPointer); - } - - // Compute what was the maximum amount of memory used in bytes - maxAllocatedMemory = maxAllocatedMemory - freeMemPointer; - - return (maxAllocatedMemory, operationsRootHash); - } - - /** - * @dev Verify and dispatch messages from the Timechain. - */ - function batchExecute(Signature calldata signature, InboundMessage calldata message) external { - uint256 initialGas = gasleft(); - // Add the solidity selector overhead to the initial gas, this way we guarantee that - // the `initialGas` represents the actual gas that was available to this contract. - initialGas = initialGas.saturatingAdd(GasUtils.BATCH_SELECTOR_OVERHEAD); - - // Execute the commands and compute the operations root hash - (, bytes32 rootHash) = _executeCommands(message.ops); - emit BatchExecuted(message.batchID); - - // Compute the Batch signing hash - rootHash = Hashing.hash(message.version, message.batchID, uint256(rootHash)); - bytes32 signingHash = - keccak256(abi.encodePacked("Analog GMP v2", NETWORK_ID, bytes32(uint256(uint160(address(this)))), rootHash)); - - // Verify the signature - _verifySignature(signature, signingHash); - - // Refund the chronicle gas - unchecked { - // Extra gas overhead used to execute the refund logic. - uint256 gasUsed = 7188; - - // Compute the gas used + base cost + proxy overhead - gasUsed = gasUsed.saturatingAdd(GasUtils.txBaseCost()); - gasUsed = gasUsed.saturatingAdd(GasUtils.proxyOverheadGasCost(uint16(msg.data.length), 0)); - gasUsed = gasUsed.saturatingAdd(initialGas - gasleft()); - - // Compute refund amount - uint256 refund = BranchlessMath.min(gasUsed.saturatingMul(tx.gasprice), address(this).balance); - - // Refund the gas used - assembly ("memory-safe") { - pop(call(gas(), caller(), refund, 0, 0, 0, 0)) - } - } - } - - function _execute(GmpCallback memory callback) private returns (GmpStatus, bytes32) { - // Verify if this GMP message was already executed - GmpInfo storage gmp = _messages[callback.eip712hash]; - require(gmp.status == GmpStatus.NOT_FOUND, "message already executed"); - - // Update status to `pending` to prevent reentrancy attacks. - gmp.status = GmpStatus.PENDING; - gmp.blockNumber = uint64(block.number); - - // Cap the GMP gas limit to 50% of the block gas limit - // OBS: we assume the remaining 50% is enough for the Gateway execution, which is a safe assumption - // once most EVM blockchains have gas limits above 10M and don't need more than 60k gas for the Gateway execution. - uint256 gasLimit = BranchlessMath.min(callback.gasLimit, block.gaslimit >> 1); - unchecked { - // Add `all but one 64th` to the gas needed, as the defined by EIP-150 - // https://eips.ethereum.org/EIPS/eip-150 - uint256 gasNeeded = gasLimit.saturatingMul(64).saturatingDiv(63); - // to guarantee it was provided enough gas to execute the GMP message - gasNeeded = gasNeeded.saturatingAdd(10000); - require(gasleft() >= gasNeeded, "insufficient gas to execute GMP message"); - } - - // Execute GMP call - bool success; - bytes32 result; - { - address dest = callback.dest; - bytes memory onGmpReceivedCallback = callback.callback; - assembly ("memory-safe") { - // Using low-level assembly because the GMP is considered executed - // regardless if the call reverts or not. - mstore(0, 0) - success := - call( - gasLimit, // call gas limit defined in the GMP message or 50% of the block gas limit - dest, // dest address - 0, // value in wei to transfer (always zero for GMP) - add(onGmpReceivedCallback, 32), // input memory pointer - mload(onGmpReceivedCallback), // input size - 0, // output memory pointer - 32 // output size (fixed 32 bytes) - ) - - // Get Result, reuse data to keep a predictable memory expansion - result := mload(0) - } - } - - // Update GMP status - GmpStatus status = - GmpStatus(BranchlessMath.ternary(success, uint256(GmpStatus.SUCCESS), uint256(GmpStatus.REVERT))); - - // Persist gmp execution status on storage - gmp.status = status; - - // Emit event - emit GmpExecuted(callback.eip712hash, callback.source, callback.dest, status, result); - - return (status, result); - } - - /** - * @dev Check if the GmpMessage network is correct and if the data is within the maximum size. - */ - function _checkGmpMessage(GmpMessage calldata message) private view { - // Theoretically we could remove the destination network field - // and fill it up with the network id of the contract, then the signature will fail. - require(message.destNetwork == NETWORK_ID, "invalid gmp network"); - - // Check if the message data is too large - require(message.data.length <= MAX_PAYLOAD_SIZE, "msg data too large"); - } - - /** - * Execute GMP message - * @param signature Schnorr signature - * @param message GMP message - */ - function execute(Signature calldata signature, GmpMessage calldata message) - external - returns (GmpStatus status, bytes32 result) - { - uint256 initialGas = gasleft(); - // Add the solidity selector overhead to the initial gas, this way we guarantee that - // the `initialGas` represents the actual gas that was available to this contract. - initialGas = initialGas.saturatingAdd(GasUtils.EXECUTION_SELECTOR_OVERHEAD); - - // Check GMP Message - _checkGmpMessage(message); - - // Convert the `GmpMessage` into `GmpCallback`, which is a more efficient representation. - // see `src/Primitives.sol` for more details. - GmpCallback memory callback = message.intoCallback(); - - // Verify the TSS Schnorr Signature - _verifySignature(signature, callback.eip712hash); - - // Execute GMP message - (status, result) = _execute(callback); - - // Refund the chronicle gas - unchecked { - // Compute GMP gas used - uint256 gasUsed = 7188 - 11; - gasUsed = gasUsed.saturatingAdd(GasUtils.txBaseCost()); - gasUsed = gasUsed.saturatingAdd(GasUtils.proxyOverheadGasCost(uint16(msg.data.length), 64)); - gasUsed = gasUsed.saturatingAdd(initialGas - gasleft()); - - // Compute refund amount - uint256 refund = BranchlessMath.min(gasUsed.saturatingMul(tx.gasprice), address(this).balance); - - assembly ("memory-safe") { - // Refund the gas used - pop(call(gas(), caller(), refund, 0, 0, 0, 0)) - } - } - } - - /** - * @dev Send message from this chain to another chain. - * @param destinationAddress the target address on the destination chain - * @param routeId the target chain where the contract call will be made - * @param executionGasLimit the gas limit available for the contract call - * @param data message data with no specified format - */ - function submitMessage(address destinationAddress, uint16 routeId, uint256 executionGasLimit, bytes calldata data) - external - payable - returns (bytes32) - { - // Check if the message data is too large - require(data.length <= MAX_PAYLOAD_SIZE, "msg data is too big"); - - // Check if the provided parameters are valid - // See `RouteStorage.estimateWeiCost` at `storage/Routes.sol` for more details. - RouteStore.NetworkInfo memory route = RouteStore.getMainStorage().get(NetworkID.wrap(routeId)); - (uint256 gasCost, uint256 fee) = route.estimateCost(data, executionGasLimit); - require(msg.value >= fee, "insufficient tx value"); - - // We use 20 bytes for represent the address and 1 bit for the contract flag - GmpSender source = msg.sender.toSender(false); - - unchecked { - // Nonce is per sender, it's incremented for every message sent. - uint64 nextNonce = uint64(_nonces[msg.sender]++); - - // Create GMP message and update nonce - GmpMessage memory message = - GmpMessage(source, NETWORK_ID, destinationAddress, routeId, uint64(executionGasLimit), nextNonce, data); - - // Emit `GmpCreated` event without copy the data, to simplify the gas estimation. - _emitGmpCreated( - message.eip712hash(), - source, - destinationAddress, - routeId, - executionGasLimit, - gasCost, - nextNonce, - message.data - ); - } - } - - /** - * @dev Emit `GmpCreated` event without copy the data, to simplify the gas estimation. - */ - function _emitGmpCreated( - bytes32 messageID, - GmpSender source, - address destinationAddress, - uint16 destinationNetwork, - uint256 executionGasLimit, - uint256 gasCost, - uint256 nonce, - bytes memory payload - ) private { - // Emit `GmpCreated` event without copy the data, to simplify the gas estimation. - // the assembly code below is equivalent to: - // ```solidity - // emit GmpCreated(prevHash, source, destinationAddress, destinationNetwork, executionGasLimit, gasCost, nonce, data); - // return prevHash; - // ``` - assembly { - let ptr := sub(payload, 0xa0) - mstore(add(ptr, 0x00), destinationNetwork) // dest network - mstore(add(ptr, 0x20), executionGasLimit) // gas limit - mstore(add(ptr, 0x40), gasCost) // gasCost - mstore(add(ptr, 0x60), nonce) // nonce - mstore(add(ptr, 0x80), 0xa0) // data offset - let size := and(add(mload(payload), 31), 0xffffffe0) - size := add(size, 192) - log4(ptr, size, GMP_CREATED_EVENT_SELECTOR, messageID, source, destinationAddress) - mstore(0, messageID) - return(0, 32) - } - } - - /*////////////////////////////////////////////////////////////// - FEE AND PAYMENT LOGIC - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Estimate the gas cost of execute a GMP message. - * @dev This function is called on the destination chain before calling the gateway to execute a source contract. - * @param networkid The target chain where the contract call will be made - * @param messageSize Message size - * @param messageSize Message gas limit - */ - function estimateMessageCost(uint16 networkid, uint256 messageSize, uint256 gasLimit) - external - view - returns (uint256) - { - RouteStore.NetworkInfo memory route = RouteStore.getMainStorage().get(NetworkID.wrap(networkid)); - - // Estimate the cost - return route.estimateWeiCost(uint16(messageSize), gasLimit); - } - - /** - * Deposit funds to the gateway contract - * IMPORTANT: this function must be called only by the administrator!!!! - */ - function deposit() external payable {} - receive() external payable {} - - /** - * Withdraw funds from the gateway contract - * @param amount The amount to withdraw - * @param recipient The recipient address - * @param data The data to send to the recipient (in case it is a contract) - */ - function withdraw(uint256 amount, address recipient, bytes calldata data) external returns (bytes memory output) { - require(msg.sender == ERC1967.getAdmin(), "unauthorized"); - // Check if the recipient is a contract - if (recipient.code.length > 0) { - bool success; - (success, output) = recipient.call{value: amount, gas: gasleft()}(data); - if (!success) { - assembly ("memory-safe") { - revert(add(output, 32), mload(output)) - } - } - } else { - payable(recipient).transfer(amount); - output = ""; - } - } - - /*////////////////////////////////////////////////////////////// - SHARDS MANAGEMENT METHODS - //////////////////////////////////////////////////////////////*/ - - /** - * @dev Register a single Shards with provided TSS public key. - */ - function _setShard(TssKey calldata publicKey) private { - bool isSuccess = ShardStore.getMainStorage().register(publicKey); - if (isSuccess) { - TssKey[] memory keys = new TssKey[](1); - keys[0] = publicKey; - emit ShardsRegistered(keys); - } - } - - /** - * @dev Revoke a single shard TSS Key. - */ - function _revokeShard(TssKey calldata publicKey) private { - bool isSuccess = ShardStore.getMainStorage().revoke(publicKey); - if (isSuccess) { - TssKey[] memory keys = new TssKey[](1); - keys[0] = publicKey; - emit ShardsUnregistered(keys); - } - } - - /** - * @dev List all shards. - */ - function shards() external view returns (TssKey[] memory) { - return ShardStore.getMainStorage().listShards(); - } - - /** - * @dev Returns the number of active shards. - */ - function shardCount() external view returns (uint256) { - return ShardStore.getMainStorage().length(); - } - - /** - * @dev Returns a shard by index. - * - Reverts with `IndexOutOfBounds` if the index is out of bounds. - */ - function shardAt(uint256 index) external view returns (TssKey memory) { - (ShardStore.ShardID xCoord, ShardStore.ShardInfo storage shard) = ShardStore.getMainStorage().at(index); - return TssKey({xCoord: uint256(ShardStore.ShardID.unwrap(xCoord)), yParity: shard.yParity + 2}); - } - - /** - * @dev Register a single Shards with provided TSS public key. - */ - function setShard(TssKey calldata publicKey) external { - require(msg.sender == ERC1967.getAdmin(), "unauthorized"); - _setShard(publicKey); - } - - /** - * @dev Register Shards in batch. - */ - function setShards(TssKey[] calldata publicKeys) external { - require(msg.sender == ERC1967.getAdmin(), "unauthorized"); - (TssKey[] memory created, TssKey[] memory revoked) = ShardStore.getMainStorage().replaceTssKeys(publicKeys); - - if (created.length > 0) { - emit ShardsRegistered(created); - } - - if (revoked.length > 0) { - emit ShardsUnregistered(revoked); - } - } - - /** - * @dev Revoke a single shard TSS Key. - */ - function revokeShard(TssKey calldata publicKey) external { - require(msg.sender == ERC1967.getAdmin(), "unauthorized"); - _revokeShard(publicKey); - } - - /** - * @dev Revoke Shards in batch. - */ - function revokeShards(TssKey[] calldata publicKeys) external { - require(msg.sender == ERC1967.getAdmin(), "unauthorized"); - TssKey[] memory revokedKeys = ShardStore.getMainStorage().revokeKeys(publicKeys); - if (revokedKeys.length > 0) { - emit ShardsUnregistered(revokedKeys); - } - } - - /*////////////////////////////////////////////////////////////// - LISTING ROUTES AND SHARDS - //////////////////////////////////////////////////////////////*/ - - /** - * @dev List all routes. - */ - function routes() external view returns (Route[] memory) { - return RouteStore.getMainStorage().listRoutes(); - } - - /** - * @dev Create or update a single route - */ - function setRoute(Route calldata info) external { - require(msg.sender == ERC1967.getAdmin(), "unauthorized"); - RouteStore.getMainStorage().createOrUpdateRoute(info); - } - - /** - * @dev Create or update an array of routes - */ - function setRoutes(Route[] calldata values) external { - require(msg.sender == ERC1967.getAdmin(), "unauthorized"); - require(values.length > 0, "routes cannot be empty"); - RouteStore.MainStorage storage store = RouteStore.getMainStorage(); - for (uint256 i = 0; i < values.length; i++) { - store.createOrUpdateRoute(values[i]); - } - } - - /*////////////////////////////////////////////////////////////// - ADMIN LOGIC - //////////////////////////////////////////////////////////////*/ - - function admin() external view returns (address) { - return ERC1967.getAdmin(); - } - - function setAdmin(address newAdmin) external payable { - require(msg.sender == ERC1967.getAdmin(), "unauthorized"); - ERC1967.setAdmin(newAdmin); - } - - // DANGER: This function is for migration purposes only, it allows the admin to set any storage slot. - function sudoSetStorage(uint256[2][] calldata values) external payable { - require(msg.sender == ERC1967.getAdmin(), "unauthorized"); - require(values.length > 0, "invalid values"); - - uint256 prev = 0; - for (uint256 i = 0; i < values.length; i++) { - uint256[2] memory entry = values[i]; - // Guarantee that the storage slot is in ascending order - // and that there are no repeated storage slots - uint256 key = entry[0]; - require(i == 0 || key > prev, "repeated storage slot"); - - // Protect admin and implementation slots - require(key != uint256(ERC1967.ADMIN_SLOT), "use setAdmin instead"); - require(key != uint256(ERC1967.IMPLEMENTATION_SLOT), "use upgrade instead"); - - // Set storage slot - uint256 value = entry[1]; - assembly { - sstore(key, value) - } - prev = key; - } - } - - function upgrade(address newImplementation) external payable { - require(msg.sender == ERC1967.getAdmin(), "unauthorized"); - - // Store the address of the implementation contract - ERC1967.setImplementation(newImplementation); - } - - function upgradeAndCall(address newImplementation, bytes memory initializer) - external - payable - returns (bytes memory returndata) - { - require(msg.sender == ERC1967.getAdmin(), "unauthorized"); - - // Store the address of the implementation contract - ERC1967.setImplementation(newImplementation); - - // Initialize storage by calling the implementation's using `delegatecall`. - bool success; - (success, returndata) = newImplementation.delegatecall(initializer); - - // Revert if the initialization failed - if (!success) { - assembly ("memory-safe") { - revert(add(returndata, 32), mload(returndata)) - } - } - } -} diff --git a/analog-gmp/src/GatewayProxy.sol b/analog-gmp/src/GatewayProxy.sol deleted file mode 100644 index 65566d251..000000000 --- a/analog-gmp/src/GatewayProxy.sol +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (src/GatewayProxy.sol) - -pragma solidity >=0.8.0; - -import {ERC1967} from "./utils/ERC1967.sol"; -import {Context, CreateKind, IUniversalFactory} from "../lib/universal-factory/src/IUniversalFactory.sol"; - -contract GatewayProxy { - /** - * @dev The address of the `UniversalFactory` contract, must be the same on all networks. - */ - IUniversalFactory internal constant FACTORY = IUniversalFactory(0x0000000000001C4Bf962dF86e38F0c10c7972C6E); - - /** - * @dev EIP-1967 storage slot with the address of the current implementation. - * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. - * Ref: https://eips.ethereum.org/EIPS/eip-1967 - */ - bytes32 private constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - - constructor(address admin) payable { - // This contract must be deployed by the `UniversalFactory` - Context memory ctx = FACTORY.context(); - require(ctx.contractAddress == address(this), "Only the UniversalFactory can deploy this contract"); - require(ctx.kind == CreateKind.CREATE2, "Only CREATE2 is allowed"); - - require(ctx.data.length > 0, "ctx.data cannot be empty length"); - require(ctx.data.length >= 128, "unexpected ctx.data format, expected 128 bytes"); - - // Store the address of the implementation contract - // DeploymentAuthorization memory authorization; - uint8 v; - bytes32 r; - bytes32 s; - address implementation; - (v, r, s, implementation) = abi.decode(ctx.data, (uint8, bytes32, bytes32, address)); - - // Verify the signature - bytes32 digest = keccak256(abi.encode(address(this), implementation)); - require(admin == ecrecover(digest, v, r, s), "invalid signature"); - - // Set the ERC1967 admin. - ERC1967.setAdmin(admin); - - // Set the ERC1967 implementation. - ERC1967.setImplementation(implementation); - } - - fallback() external payable { - assembly ("memory-safe") { - // Copy the calldata to memory - calldatacopy(0, 0, calldatasize()) - - // Delegate call to the implementation contract - let success := delegatecall(gas(), sload(IMPLEMENTATION_SLOT), 0, calldatasize(), 0, 0) - - // Copy the return data to memory - returndatacopy(0, 0, returndatasize()) - - // Return if the call succeeded - if success { return(0, returndatasize()) } - - // Revert if the call failed - revert(0, returndatasize()) - } - } -} diff --git a/analog-gmp/src/NetworkID.sol b/analog-gmp/src/NetworkID.sol deleted file mode 100644 index 51e217cbb..000000000 --- a/analog-gmp/src/NetworkID.sol +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (src/NetworkID.sol) - -pragma solidity ^0.8.0; - -import {BranchlessMath} from "./utils/BranchlessMath.sol"; - -type NetworkID is uint16; - -library NetworkIDHelpers { - NetworkID internal constant MAINNET = NetworkID.wrap(0); - NetworkID internal constant ASTAR = NetworkID.wrap(1); - NetworkID internal constant POLYGON_POS = NetworkID.wrap(2); - NetworkID internal constant ETHEREUM_LOCAL_DEV = NetworkID.wrap(3); - NetworkID internal constant GOERLI = NetworkID.wrap(4); - NetworkID internal constant SEPOLIA = NetworkID.wrap(5); - NetworkID internal constant ASTAR_LOCAL_DEV = NetworkID.wrap(6); - NetworkID internal constant SHIBUYA = NetworkID.wrap(7); - NetworkID internal constant POLYGON_AMOY = NetworkID.wrap(8); - NetworkID internal constant BINANCE_SMART_CHAIN_TESTNET = NetworkID.wrap(9); - NetworkID internal constant ARBITRUM_SEPOLIA = NetworkID.wrap(10); - - /** - * @dev Converts a `NetworkID` into a `uint16`. - */ - function asUint(NetworkID networkId) internal pure returns (uint16) { - return NetworkID.unwrap(networkId); - } - - /** - * @dev Get the EIP-150 chain id from the network id. - */ - function chainId(NetworkID networkId) internal pure returns (uint64 chainID) { - assembly { - switch networkId - case 0 { - // Ethereum Mainnet - chainID := 0 - } - case 1 { - // Astar - chainID := 592 - } - case 2 { - // Polygon PoS - chainID := 137 - } - case 3 { - // Ethereum local testnet - chainID := 1337 - } - case 4 { - // Goerli - chainID := 5 - } - case 5 { - // Sepolia - chainID := 11155111 - } - case 6 { - // Astar local testnet - chainID := 592 - } - case 7 { - // Shibuya - chainID := 81 - } - case 8 { - // Polygon Amoy - chainID := 80002 - } - case 9 { - // Binance Smart Chain - chainID := 97 - } - case 10 { - // Arbitrum Sepolia - chainID := 421614 - } - default { - // Unknown network id - chainID := 0xffffffffffffffff - } - } - require(chainID > 2 ** 24, "the provided network id doesn't exists"); - return uint64(chainID); - } - - /** - * @dev Try to get the network id from the chain id. - */ - function tryFromChainID(uint256 chainid) internal pure returns (bool, NetworkID) { - uint256 networkId = type(uint256).max; - - // Ethereum Mainnet - networkId = BranchlessMath.ternary(chainid == 0, asUint(MAINNET), networkId); - // Astar - networkId = BranchlessMath.ternary(chainid == 592, asUint(ASTAR), networkId); - // Polygon PoS - networkId = BranchlessMath.ternary(chainid == 137, asUint(POLYGON_POS), networkId); - // Ethereum local testnet - networkId = BranchlessMath.ternary(chainid == 1337, asUint(ETHEREUM_LOCAL_DEV), networkId); - // Goerli - networkId = BranchlessMath.ternary(chainid == 5, asUint(GOERLI), networkId); - // Sepolia - networkId = BranchlessMath.ternary(chainid == 11155111, asUint(SEPOLIA), networkId); - // Astar local testnet - networkId = BranchlessMath.ternary(chainid == 592, asUint(ASTAR_LOCAL_DEV), networkId); - // Shibuya - networkId = BranchlessMath.ternary(chainid == 81, asUint(SHIBUYA), networkId); - // Polygon Amoy - networkId = BranchlessMath.ternary(chainid == 80002, asUint(POLYGON_AMOY), networkId); - // Binance Smart Chain - networkId = BranchlessMath.ternary(chainid == 97, asUint(BINANCE_SMART_CHAIN_TESTNET), networkId); - // Arbitrum Sepolia - networkId = BranchlessMath.ternary(chainid == 421614, asUint(ARBITRUM_SEPOLIA), networkId); - - bool exists = networkId != type(uint256).max; - return (exists, NetworkID.wrap(uint16(networkId))); - } - - /** - * @dev Converts a EIP-155 chain id into a `NetworkID`, reverts if the network id doesn't exists. - */ - function fromChainID(uint256 chainid) internal pure returns (NetworkID) { - (bool exists, NetworkID networkId) = tryFromChainID(chainid); - require(exists, "network id doesn't exists for the given chain id"); - return networkId; - } -} diff --git a/analog-gmp/src/Primitives.sol b/analog-gmp/src/Primitives.sol deleted file mode 100644 index b4e879335..000000000 --- a/analog-gmp/src/Primitives.sol +++ /dev/null @@ -1,376 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (src/Primitives.sol) - -pragma solidity >=0.8.0; - -import {BranchlessMath} from "./utils/BranchlessMath.sol"; -import {UFloatMath, UFloat9x56} from "./utils/Float9x56.sol"; -import {NetworkID} from "./NetworkID.sol"; - -/** - * @dev GMP message EIP-712 Type Hash. - * Declared as raw value to enable it to be used in inline assembly - * keccak256("GmpMessage(bytes32 source,uint16 srcNetwork,address dest,uint16 destNetwork,uint64 gasLimit,uint64 gasCost,uint32 nonce,bytes data)") - */ -uint256 constant GMP_VERSION = 0; - -/** - * @dev Maximum size of the GMP payload - */ -uint256 constant MAX_PAYLOAD_SIZE = 0x6000; - -/** - * @dev GmpSender is the sender of a GMP message - */ -type GmpSender is bytes32; - -/** - * @dev Tss public key - * @param yParity public key y-coord parity, the contract converts it to 27/28 - * @param xCoord affine x-coordinate - */ -struct TssKey { - uint8 yParity; - uint256 xCoord; -} - -/** - * @dev Schnorr signature. - * OBS: what is actually signed is: keccak256(abi.encodePacked(R, parity, px, nonce, message)) - * Where `parity` is the public key y coordinate stored in the contract, and `R` is computed from `e` and `s` parameters. - * @param xCoord public key x coordinates, y-parity is stored in the contract - * @param e Schnorr signature e component - * @param s Schnorr signature s component - */ -struct Signature { - uint256 xCoord; - uint256 e; - uint256 s; -} - -/** - * @dev GMP payload, this is what the timechain creates as task payload - * @param source Pubkey/Address of who send the GMP message - * @param srcNetwork Source chain identifier (for ethereum networks it is the EIP-155 chain id) - * @param dest Destination/Recipient contract address - * @param destNetwork Destination chain identifier (it's the EIP-155 chain_id for ethereum networks) - * @param gasLimit gas limit of the GMP call - * @param nonce Sequence nonce per sender, allows sending two messages with same content - * @param data message data with no specified format - */ -struct GmpMessage { - GmpSender source; - uint16 srcNetwork; - address dest; - uint16 destNetwork; - uint64 gasLimit; - uint64 nonce; - bytes data; -} - -/** - * @dev Message payload used to revoke or/and register new shards - * @param revoke Shard's keys to revoke - * @param register Shard's keys to register - */ -struct UpdateKeysMessage { - TssKey[] revoke; - TssKey[] register; -} - -/** - * @dev Messages from Timechain take the form of these commands. - */ -enum Command { - Invalid, - GMP, - RegisterShard, - UnregisterShard, - SetRoute -} - -/** - * @dev Inbound message from a Timechain - * @param command Command identifier. - * @param params Encoded command. - */ -struct GatewayOp { - /// @dev The command to execute - Command command; - /// @dev The Parameters for the command - bytes params; -} - -/** - * @dev Inbound message from a Timechain - * @param version Message version, will change if the message format changes. - * @param batchID Sequence number representing the batch order. - * @param ops List of operations to execute. - */ -struct InboundMessage { - uint8 version; - /// @dev The batch ID - uint64 batchID; - /// @dev - GatewayOp[] ops; -} - -/** - * @dev A Route represents a communication channel between two networks. - * @param networkId The id of the provided network. - * @param gasLimit The maximum amount of gas we allow on this particular network. - * @param gateway Destination chain gateway address. - * @param relativeGasPriceNumerator Gas price numerator in terms of the source chain token. - * @param relativeGasPriceDenominator Gas price denominator in terms of the source chain token. - */ -struct Route { - NetworkID networkId; - uint64 gasLimit; - uint128 baseFee; - bytes32 gateway; - uint128 relativeGasPriceNumerator; - uint128 relativeGasPriceDenominator; -} - -/** - * @dev Message payload used to revoke or/and register new shards - * @param revoke Shard's keys to revoke - * @param register Shard's keys to register - */ -struct Network { - uint16 id; - address gateway; -} - -/** - * @dev Status of a GMP message - */ -enum GmpStatus { - NOT_FOUND, - SUCCESS, - REVERT, - INSUFFICIENT_FUNDS, - PENDING -} - -/** - * @dev GmpMessage with EIP-712 GMP ID and callback function encoded. - * @param eip712hash EIP-712 hash of the `GmpMessage`, which is it's unique identifier - * @param source Pubkey/Address of who send the GMP message - * @param srcNetwork Source chain identifier (for ethereum networks it is the EIP-155 chain id) - * @param dest Destination/Recipient contract address - * @param destNetwork Destination chain identifier (it's the EIP-155 chain_id for ethereum networks) - * @param gasLimit gas limit of the GMP call - * @param nonce Sequence nonce per sender, allows sending two messages with same content - * @param callback encoded callback of `IGmpRecipient` interface, see `IGateway.sol` for more details. - */ -struct GmpCallback { - bytes32 eip712hash; - GmpSender source; - uint16 srcNetwork; - address dest; - uint16 destNetwork; - uint64 gasLimit; - uint64 nonce; - bytes callback; -} - -/** - * @dev EIP-712 utility functions for primitives - */ -library PrimitiveUtils { - function toAddress(GmpSender sender) internal pure returns (address) { - return address(uint160(uint256(GmpSender.unwrap(sender)))); - } - - function toSender(address addr, bool isContract) internal pure returns (GmpSender) { - uint256 sender = BranchlessMath.toUint(isContract) << 160 | uint256(uint160(addr)); - return GmpSender.wrap(bytes32(sender)); - } - - // computes the hash of an array of tss keys - function eip712hash(TssKey memory tssKey) internal pure returns (bytes32) { - return keccak256(abi.encode(keccak256("TssKey(uint8 yParity,uint256 xCoord)"), tssKey.yParity, tssKey.xCoord)); - } - - // computes the hash of an array of tss keys - function eip712hash(TssKey[] memory tssKeys) internal pure returns (bytes32) { - bytes memory keysHashed = new bytes(tssKeys.length * 32); - uint256 ptr; - assembly { - ptr := keysHashed - } - for (uint256 i = 0; i < tssKeys.length; i++) { - bytes32 hash = eip712hash(tssKeys[i]); - assembly { - ptr := add(ptr, 32) - mstore(ptr, hash) - } - } - return keccak256(keysHashed); - } - - // computes the hash of the fully encoded EIP-712 message for the domain, which can be used to recover the signer - function eip712hash(UpdateKeysMessage memory message) internal pure returns (bytes32) { - return keccak256( - abi.encode( - keccak256("UpdateKeysMessage(TssKey[] revoke,TssKey[] register)TssKey(uint8 yParity,uint256 xCoord)"), - eip712hash(message.revoke), - eip712hash(message.register) - ) - ); - } - - function eip712hash(GmpMessage memory message) internal pure returns (bytes32 id) { - bytes memory data = message.data; - assembly ("memory-safe") { - // keccak256(message.data) - id := keccak256(add(data, 32), mload(data)) - - // now compute the GmpMessage Type Hash without memory copying - let offset := sub(message, 32) - let backup := mload(offset) - { - mstore(offset, GMP_VERSION) - { - let offset2 := add(offset, 0xe0) - let backup2 := mload(offset2) - mstore(offset2, id) - id := keccak256(offset, 0x100) - mstore(offset2, backup2) - } - } - mstore(offset, backup) - } - } - - type MessagePtr is uint256; - - function _intoMemoryPointer(MessagePtr ptr) private pure returns (GmpMessage memory r) { - assembly { - r := ptr - } - } - - function _intoCalldataPointer(MessagePtr ptr) private pure returns (GmpMessage calldata r) { - assembly { - r := ptr - } - } - - function memToCallback(GmpMessage memory message) internal pure returns (GmpCallback memory callback) { - MessagePtr ptr; - assembly { - ptr := message - } - _intoCallback(ptr, false, callback); - } - - function intoCallback(GmpMessage calldata message) internal pure returns (GmpCallback memory callback) { - MessagePtr ptr; - assembly { - ptr := message - } - _intoCallback(ptr, true, callback); - } - - /** - * @dev Computes the message ID from the provided `GmpCallback` struct. - */ - function _computeMessageID(GmpCallback memory callback) private pure { - bytes memory onGmpReceived = callback.callback; - bytes32 dataHash; - assembly ("memory-safe") { - let offset := add(onGmpReceived, 0xc4) - dataHash := keccak256(add(offset, 0x20), mload(offset)) - } - callback.eip712hash = bytes32(GMP_VERSION); - assembly ("memory-safe") { - // temporarily store the result at `0x00e0..0x0100`, which is the `GmpCallback.callback.offset` field. - mstore(add(callback, 0xe0), dataHash) - - // Compute `keccak256(abi.encode(GMP_VERSION, message.source, ..., keccak256(message.data)))` - dataHash := keccak256(callback, 0x0100) - - // Replace the `eip712hash` by the `callback.data.offset`. - mstore(add(callback, 0xe0), onGmpReceived) - - // Replace the `id` in `onGmpReceived(uint256 id,...)` in the callback. - mstore(add(onGmpReceived, 0x24), dataHash) - } - callback.eip712hash = dataHash; - } - - /** - * @dev Converts the `GmpMessage` into a `GmpCallback` struct, which contains all fields from - * `GmpMessage`, plus the EIP-712 hash and `IGmpReceiver.onGmpReceived` callback. - * - * This method also prevents copying the `message.data` to memory twice, which is expensive if - * the data is large. - * Example: using solidity high-level `abi.encode` method does the following. - * 1. Copy the `message.data` to memory to compute the `GmpMessage` EIP-712 hash. - * 2. Copy again to encode the `IGmpReceiver.onGmpReceived` callback. - * - * Instead we copy it once and use the same memory location to compute the EIP-712 hash and - * create he `IGmpReceiver.onGmpReceived` callback, unfortunately this requires inline assembly. - * - * @param message GmpMessage from calldata to be encoded - * @param callback `GmpCallback` struct - */ - function _intoCallback(MessagePtr message, bool isCalldata, GmpCallback memory callback) private pure { - // | MEMORY OFFSET | RESERVED FIELD | - // | 0x0000..0x0020 <- GmpCallback.eip712hash - // | 0x0020..0x0040 <- GmpCallback.source - // | 0x0040..0x0060 <- GmpCallback.srcNetwork - // | 0x0060..0x0080 <- GmpCallback.dest - // | 0x0080..0x00a0 <- GmpCallback.destNetwork - // | 0x00a0..0x00c0 <- GmpCallback.gasLimit - // | 0x00c0..0x00e0 <- GmpCallback.nonce - // | 0x00e0..0x0100 <- GmpCallback.callback.offset - // | 0x0100..0x0120 <- GmpCallback.callback.length - // | 0x0120..0x0124 <- onGmpReceived.selector (4 bytes) - // | 0x0124..0x0144 <- onGmpReceived.id - // | 0x0144..0x0164 <- onGmpReceived.network - // | 0x0164..0x0184 <- onGmpReceived.source - // | 0x0184..0x01a4 <- onGmpReceived.nonce - // | 0x01a4..0x01c4 <- onGmpReceived.data.offset - // | 0x01c4..0x01e4 <- onGmpReceived.data.length - // | 0x01e4........ <- onGmpReceived.data - if (isCalldata) { - GmpMessage calldata m = _intoCalldataPointer(message); - callback.source = m.source; - callback.srcNetwork = m.srcNetwork; - callback.dest = m.dest; - callback.destNetwork = m.destNetwork; - callback.gasLimit = m.gasLimit; - callback.nonce = m.nonce; - bytes calldata data = m.data; - callback.callback = abi.encodeWithSignature( - "onGmpReceived(bytes32,uint128,bytes32,uint64,bytes)", - callback.eip712hash, - callback.srcNetwork, - callback.source, - callback.nonce, - data - ); - } else { - GmpMessage memory m = _intoMemoryPointer(message); - callback.source = m.source; - callback.srcNetwork = m.srcNetwork; - callback.dest = m.dest; - callback.destNetwork = m.destNetwork; - callback.gasLimit = m.gasLimit; - callback.nonce = m.nonce; - callback.callback = abi.encodeWithSignature( - "onGmpReceived(bytes32,uint128,bytes32,uint64,bytes)", - callback.eip712hash, - callback.srcNetwork, - callback.source, - callback.nonce, - m.data - ); - } - // Compute the message ID - _computeMessageID(callback); - } -} diff --git a/analog-gmp/src/interfaces/IExecutor.sol b/analog-gmp/src/interfaces/IExecutor.sol deleted file mode 100644 index 6272dd654..000000000 --- a/analog-gmp/src/interfaces/IExecutor.sol +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (src/interfaces/IExecutor.sol) - -pragma solidity >=0.8.0; - -import { - InboundMessage, - Signature, - GmpMessage, - TssKey, - GmpStatus, - GmpStatus, - UpdateKeysMessage, - GmpSender, - Route -} from "../Primitives.sol"; - -/** - * @dev Required interface of an Gateway compliant contract - */ -interface IExecutor { - /** - * @dev Emitted when `GmpMessage` is executed. - * @param id EIP-712 hash of the `GmpPayload`, which is it's unique identifier - * @param source sender pubkey/address (the format depends on src chain) - * @param dest recipient address - * @param status GMP message execution status - * @param result GMP result - */ - event GmpExecuted( - bytes32 indexed id, GmpSender indexed source, address indexed dest, GmpStatus status, bytes32 result - ); - - /** - * @dev Emitted when a Batch is executed. - * @param batch batch_id which is executed - */ - event BatchExecuted(uint64 batch); - - /** - * @dev Emitted when shards are registered. - * @param keys registered shard's keys - */ - event ShardsRegistered(TssKey[] keys); - - /** - * @dev Emitted when shards are unregistered. - * @param keys unregistered shard's keys - */ - event ShardsUnregistered(TssKey[] keys); - - /** - * @dev List all shards currently registered in the gateway. - */ - function shards() external returns (TssKey[] memory); - - function setShard(TssKey calldata publicKey) external; - - /** - * @dev Register Shards in batch. - */ - function setShards(TssKey[] calldata publicKeys) external; - - /** - * @dev Revoke a single shard TSS Key. - */ - function revokeShard(TssKey calldata publicKey) external; - - /** - * @dev Revoke a single shard TSS Key. - */ - function revokeShards(TssKey[] calldata publicKey) external; - - /** - * @dev List all shards currently registered in the gateway. - */ - function routes() external returns (Route[] memory); - - function setRoute(Route calldata info) external; - - /** - * @dev Create or update an array of routes - */ - function setRoutes(Route[] calldata values) external; - - /** - * Execute operatins in batch - * @param signature Schnorr signature - * @param message GMP message - */ - function batchExecute(Signature calldata signature, InboundMessage calldata message) external; - - /** - * Execute GMP message - * @param signature Schnorr signature - * @param message GMP message - */ - function execute(Signature calldata signature, GmpMessage calldata message) - external - returns (GmpStatus status, bytes32 result); - - /** - * Deposit funds to the gateway contract - */ - function deposit() external payable; -} diff --git a/analog-gmp/src/interfaces/IGateway.sol b/analog-gmp/src/interfaces/IGateway.sol deleted file mode 100644 index e61d83c40..000000000 --- a/analog-gmp/src/interfaces/IGateway.sol +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (src/interfaces/IGateway.sol) - -pragma solidity >=0.8.0; - -import {GmpSender} from "../Primitives.sol"; - -/** - * @dev Required interface of an Gateway compliant contract - */ -interface IGateway { - /** - * @dev New GMP submitted by calling the `submitMessage` method. - * @param id EIP-712 hash of the `GmpPayload`, which is it's unique identifier - * @param source sender account, with an extra flag indicating if it is a contract or an EOA - * @param destinationAddress the target address on the destination chain. - * @param destinationNetwork the target chain where the contract call will be made. - * @param executionGasLimit the gas limit available for the contract call - * @param gasCost the gas limit available for the contract call - * @param nonce Sequence number per sender, used to guarantee each message is unique. - * @param data message data with no specified format - */ - event GmpCreated( - bytes32 indexed id, - bytes32 indexed source, - address indexed destinationAddress, - uint16 destinationNetwork, - uint64 executionGasLimit, - uint64 gasCost, - uint64 nonce, - bytes data - ); - - function networkId() external view returns (uint16); - - /** - * @notice Estimate the gas cost of execute a GMP message. - * @dev This function is called on the destination chain before calling the gateway to execute a source contract. - * @param networkid The target chain where the contract call will be made - * @param messageSize Message size - * @param messageSize Message gas limit - */ - function estimateMessageCost(uint16 networkid, uint256 messageSize, uint256 gasLimit) - external - view - returns (uint256); - - /** - * @dev Send message from chain A to chain B - * @param destinationAddress the target address on the destination chain - * @param destinationNetwork the target chain where the contract call will be made - * @param executionGasLimit the gas limit available for the contract call - * @param data message data with no specified format - */ - function submitMessage( - address destinationAddress, - uint16 destinationNetwork, - uint256 executionGasLimit, - bytes calldata data - ) external payable returns (bytes32); -} diff --git a/analog-gmp/src/interfaces/IGmpReceiver.sol b/analog-gmp/src/interfaces/IGmpReceiver.sol deleted file mode 100644 index 85c1a62a2..000000000 --- a/analog-gmp/src/interfaces/IGmpReceiver.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (src/interfaces/IGmpReceiver.sol) - -pragma solidity >=0.8.0; - -/** - * @dev Required interface of an GMP compliant contract - */ -interface IGmpReceiver { - /** - * @dev Handles the receipt of a single GMP message. - * The contract must verify the msg.sender, it must be the Gateway Contract address. - * - * @param id The EIP-712 hash of the message payload, used as GMP unique identifier - * @param network The chain_id of the source chain who send the message - * @param source The pubkey/address which sent the GMP message - * @param payload The message payload with no specified format - * @return 32 byte result which will be stored together with GMP message - */ - function onGmpReceived(bytes32 id, uint128 network, bytes32 source, uint64 nonce, bytes calldata payload) - external - payable - returns (bytes32); -} diff --git a/analog-gmp/src/interfaces/IUpgradable.sol b/analog-gmp/src/interfaces/IUpgradable.sol deleted file mode 100644 index e6d694be5..000000000 --- a/analog-gmp/src/interfaces/IUpgradable.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (src/interfaces/IUpgradable.sol) - -pragma solidity >=0.8.0; - -interface IUpgradable { - // The new implementation address is a not a contract - error InvalidContract(); - // The supplied codehash does not match the new implementation codehash - error InvalidCodeHash(); - - // The implementation contract was upgraded - event Upgraded(address indexed implementation); -} diff --git a/analog-gmp/src/storage/Routes.sol b/analog-gmp/src/storage/Routes.sol deleted file mode 100644 index 0c7f7984a..000000000 --- a/analog-gmp/src/storage/Routes.sol +++ /dev/null @@ -1,286 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (src/storage/Routes.sol) -pragma solidity ^0.8.20; - -import {Signature, Network, Route, MAX_PAYLOAD_SIZE} from "../Primitives.sol"; -import {NetworkIDHelpers, NetworkID} from "../NetworkID.sol"; -import {EnumerableSet, Pointer} from "../utils/EnumerableSet.sol"; -import {BranchlessMath} from "../utils/BranchlessMath.sol"; -import {UFloat9x56, UFloatMath} from "../utils/Float9x56.sol"; -import {StoragePtr} from "../utils/Pointer.sol"; -import {GasUtils} from "../utils/GasUtils.sol"; - -/** - * @dev EIP-7201 Route's Storage - */ -library RouteStore { - using Pointer for StoragePtr; - using Pointer for uint256; - using EnumerableSet for EnumerableSet.Map; - using NetworkIDHelpers for NetworkID; - using UFloatMath for UFloat9x56; - using BranchlessMath for uint256; - - /** - * @dev Namespace of the routes storage `analog.one.gateway.routes`. - * keccak256(abi.encode(uint256(keccak256("analog.one.gateway.routes")) - 1)) & ~bytes32(uint256(0xff)); - */ - bytes32 internal constant _EIP7201_NAMESPACE = 0xb184f2aad520cf7f1f1270909517c75ae33cdf2bd7d32b997a96577f11a48800; - - /** - * @dev Network info stored in the Gateway Contract - * @param gasLimit The maximum amount of gas we allow on this particular network. - * @param relativeGasPrice Gas price of destination chain, in terms of the source chain token. - * @param baseFee Base fee for cross-chain message approval on destination, in terms of source native gas token. - */ - struct NetworkInfo { - uint64 gasLimit; - UFloat9x56 relativeGasPrice; - uint128 baseFee; - } - - /** - * @dev Emitted when a route is updated. - * @param networkId Network identifier. - * @param relativeGasPrice Gas price of destination chain, in terms of the source chain token. - * @param baseFee Base fee for cross-chain message approval on destination, in terms of source native gas token. - * @param gasLimit The maximum amount of gas we allow on this particular network. - */ - event RouteUpdated(uint16 indexed networkId, UFloat9x56 relativeGasPrice, uint128 baseFee, uint64 gasLimit); - - /** - * @dev Shard info stored in the Gateway Contract - * OBS: the order of the attributes matters! ethereum storage is 256bit aligned, try to keep - * the shard info below 256 bit, so it can be stored in one single storage slot. - * reference: https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html - * - * @custom:storage-location erc7201:analog.one.gateway.routes - */ - struct MainStorage { - EnumerableSet.Map routes; - } - - error RouteNotExists(NetworkID id); - error IndexOutOfBounds(uint256 index); - - function getMainStorage() internal pure returns (MainStorage storage $) { - assembly { - $.slot := _EIP7201_NAMESPACE - } - } - - /** - * @dev Converts a `StoragePtr` into an `NetworkInfo`. - */ - function pointerToRoute(StoragePtr ptr) private pure returns (NetworkInfo storage route) { - assembly { - route.slot := ptr - } - } - - /** - * @dev Returns true if the value is in the set. O(1). - */ - function has(MainStorage storage store, NetworkID id) internal view returns (bool) { - return store.routes.has(bytes32(uint256(id.asUint()))); - } - - /** - * @dev Get or create a value. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ - function getOrAdd(MainStorage storage store, NetworkID id) private returns (bool, NetworkInfo storage) { - (bool success, StoragePtr ptr) = store.routes.tryAdd(bytes32(uint256(id.asUint()))); - return (success, pointerToRoute(ptr)); - } - - /** - * @dev Removes a value from a set. O(1). - * - * Returns true if the value was removed from the set, that is if it was - * present. - */ - function remove(MainStorage storage store, NetworkID id) internal returns (bool) { - StoragePtr ptr = store.routes.remove(bytes32(uint256(id.asUint()))); - if (ptr.isNull()) { - return false; - } - return true; - } - - /** - * @dev Returns the number of values on the set. O(1). - */ - function length(MainStorage storage store) internal view returns (uint256) { - return store.routes.length(); - } - - /** - * @dev Returns the value stored at position `index` in the set. O(1). - * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. - * - * Requirements: - * - * - `index` must be strictly less than {length}. - */ - function at(MainStorage storage store, uint256 index) internal view returns (NetworkID, NetworkInfo storage) { - (bytes32 key, StoragePtr value) = store.routes.at(index); - if (value.isNull()) { - revert IndexOutOfBounds(index); - } - return (NetworkID.wrap(uint16(uint256(key))), pointerToRoute(value)); - } - - /** - * @dev Returns the value associated with `NetworkInfo`. O(1). - * - * Requirements: - * - `NetworkInfo` must be in the map. - */ - function get(MainStorage storage store, NetworkID id) internal view returns (NetworkInfo storage) { - StoragePtr ptr = store.routes.get(bytes32(uint256(id.asUint()))); - if (ptr.isNull()) { - revert RouteNotExists(id); - } - return pointerToRoute(ptr); - } - - /** - * @dev Returns the value associated with `NetworkInfo`. O(1). - */ - function tryGet(MainStorage storage store, NetworkID id) internal view returns (bool, NetworkInfo storage) { - (bool exists, StoragePtr ptr) = store.routes.tryGet(bytes32(uint256(id.asUint()))); - return (exists, pointerToRoute(ptr)); - } - - function createOrUpdateRoute(MainStorage storage store, Route calldata route) internal { - // Update network info - (bool created, NetworkInfo storage stored) = getOrAdd(store, route.networkId); - require((created && route.gateway != bytes32(0)) || !created, "domain separator cannot be zero"); - - // Update gas limit if it's not zero - if (route.gasLimit > 0) { - stored.gasLimit = route.gasLimit; - } - - // Update relative gas price and base fee if any of them are greater than zero - if (route.relativeGasPriceDenominator > 0) { - UFloat9x56 relativeGasPrice = - UFloatMath.fromRational(route.relativeGasPriceNumerator, route.relativeGasPriceDenominator); - stored.relativeGasPrice = relativeGasPrice; - stored.baseFee = route.baseFee; - } - - emit RouteUpdated(route.networkId.asUint(), stored.relativeGasPrice, stored.baseFee, stored.gasLimit); - } - - /** - * @dev Storage initializer function, used to set up the initial storage of the contract. - * @param store Storage location. - * @param networks List of networks to initialize. - * @param networkdID The network id of this chain. - */ - function initialize(MainStorage storage store, Network[] calldata networks, NetworkID networkdID) internal { - for (uint256 i = 0; i < networks.length; i++) { - Network calldata network = networks[i]; - (bool created, NetworkInfo storage info) = getOrAdd(store, NetworkID.wrap(network.id)); - require(created, "network already initialized"); - require(network.id != networkdID.asUint() || network.gateway == address(this), "wrong gateway address"); - info.gasLimit = 15_000_000; // Default to 15M gas - info.relativeGasPrice = UFloatMath.ONE; - info.baseFee = 0; - } - } - - /** - * @dev Return all routes registered currently registered. - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - */ - function listRoutes(MainStorage storage store) internal view returns (Route[] memory) { - bytes32[] memory idx = store.routes.keys; - Route[] memory routes = new Route[](idx.length); - for (uint256 i = 0; i < idx.length; i++) { - (bool success, NetworkInfo storage route) = tryGet(store, NetworkID.wrap(uint16(uint256(idx[i])))); - require(success, "route not found"); - (uint256 numerator, uint256 denominator) = route.relativeGasPrice.toRational(); - routes[i] = Route({ - networkId: NetworkID.wrap(uint16(uint256(idx[i]))), - gasLimit: route.gasLimit, - baseFee: route.baseFee, - gateway: bytes32(uint256(uint160(address(this)))), - relativeGasPriceNumerator: uint128(numerator), - relativeGasPriceDenominator: uint128(denominator) - }); - } - return routes; - } - - /** - * @dev Check a few preconditions before estimate the GMP wei cost. - */ - function _checkPreconditions(NetworkInfo memory route, uint256 messageSize, uint256 gasLimit) private pure { - // Verify if the network exists - require(route.baseFee > 0 || UFloat9x56.unwrap(route.relativeGasPrice) > 0, "route is temporarily disabled"); - - // Verify if the gas limit and message size are within the limits - require(gasLimit <= route.gasLimit, "gas limit exceeded"); - require(messageSize <= MAX_PAYLOAD_SIZE, "maximum payload size exceeded"); - } - - /** - * @dev Utility function for measure the wei cost of a GMP message. - */ - function estimateCost(NetworkInfo memory route, bytes calldata data, uint256 gasLimit) - internal - pure - returns (uint256 gasCost, uint256 fee) - { - // Guarantee the networks exists and `data` is less than `MAX_PAYLOAD_SIZE` - _checkPreconditions(route, data.length, gasLimit); - - // Compute base cost - uint256 nonZeros = GasUtils.countNonZerosCalldata(data); - uint256 zeros = data.length - nonZeros; - - // Compute execution cost - gasCost = GasUtils.estimateGas(uint16(nonZeros), uint16(zeros), gasLimit); - - // Calculate the gas cost: gasPrice * gasCost + baseFee - fee = UFloatMath.saturatingMul(route.relativeGasPrice, gasCost).saturatingAdd(route.baseFee); - } - - /** - * @dev Utility function for measure the wei cost of a GMP message. - */ - function estimateWeiCost(NetworkInfo memory route, bytes calldata data, uint256 gasLimit) - internal - pure - returns (uint256) - { - _checkPreconditions(route, data.length, gasLimit); - uint256 nonZeros = GasUtils.countNonZerosCalldata(data); - uint256 zeros = data.length - nonZeros; - return - GasUtils.estimateWeiCost(route.relativeGasPrice, route.baseFee, uint16(nonZeros), uint16(zeros), gasLimit); - } - - /** - * @dev Utility function for measure the wei cost of a GMP message. - */ - function estimateWeiCost(NetworkInfo memory route, uint256 messageSize, uint256 gasLimit) - internal - pure - returns (uint256) - { - _checkPreconditions(route, messageSize, gasLimit); - return GasUtils.estimateWeiCost(route.relativeGasPrice, route.baseFee, uint16(messageSize), 0, gasLimit); - } -} diff --git a/analog-gmp/src/storage/Shards.sol b/analog-gmp/src/storage/Shards.sol deleted file mode 100644 index 26aed8e94..000000000 --- a/analog-gmp/src/storage/Shards.sol +++ /dev/null @@ -1,382 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (src/storage/Shards.sol) -pragma solidity ^0.8.20; - -import {TssKey, Signature} from "../Primitives.sol"; -import {EnumerableSet, Pointer} from "../utils/EnumerableSet.sol"; -import {BranchlessMath} from "../utils/BranchlessMath.sol"; -import {StoragePtr} from "../utils/Pointer.sol"; - -library _ShardStore { - function from(uint256 xCoord) internal pure returns (ShardStore.ShardID) { - return ShardStore.ShardID.wrap(bytes32(xCoord)); - } - - /** - * @dev Converts a `StoragePtr` into a `ShardInfo`. - */ - function asShardInfo(StoragePtr ptr) internal pure returns (ShardStore.ShardInfo storage info) { - assembly { - info.slot := ptr - } - } - - /** - * @dev Converts a `ShardInfo` into a `StoragePtr`. - */ - function asPtr(ShardStore.ShardInfo storage info) internal pure returns (StoragePtr ptr) { - assembly { - ptr := info.slot - } - } -} - -/** - * @dev EIP-7201 Shard's Storage - */ -library ShardStore { - using Pointer for StoragePtr; - using Pointer for uint256; - using EnumerableSet for EnumerableSet.Map; - using _ShardStore for uint256; - using _ShardStore for StoragePtr; - using _ShardStore for ShardInfo; - - /** - * @dev Namespace of the shards storage `analog.one.gateway.shards`. - * keccak256(abi.encode(uint256(keccak256("analog.one.gateway.shards")) - 1)) & ~bytes32(uint256(0xff)); - */ - bytes32 internal constant _EIP7201_NAMESPACE = 0x582bcdebbeef4fb96dde802cfe96e9942657f4bedb5cfe94e8786bb683eb1f00; - - uint8 internal constant SHARD_ACTIVE = (1 << 0); // Shard active bitflag - uint8 internal constant SHARD_Y_PARITY = (1 << 1); // Pubkey y parity bitflag - - /** - * @dev Shard ID, this is the xCoord of the TssKey - */ - type ShardID is bytes32; - - /** - * @dev Shard info stored in the Gateway Contract - * OBS: the order of the attributes matters! ethereum storage is 256bit aligned, try to keep - * the shard info below 256 bit, so it can be stored in one single storage slot. - * reference: https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html - * - * @custom:storage-location erc7201:analog.one.gateway.shards - */ - struct ShardInfo { - uint8 yParity; - uint32 nonce; - uint64 createdAtBlock; - } - - /** - * @dev Shard info stored in the Gateway Contract - * OBS: the order of the attributes matters! ethereum storage is 256bit aligned, try to keep - * the shard info below 256 bit, so it can be stored in one single storage slot. - * reference: https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html - * - * @custom:storage-location erc7201:analog.one.gateway.shards - */ - struct MainStorage { - EnumerableSet.Map shards; - } - - error ShardAlreadyRegistered(ShardID id); - error ShardNotExists(ShardID id); - error IndexOutOfBounds(uint256 index); - - function getMainStorage() internal pure returns (MainStorage storage $) { - assembly { - $.slot := _EIP7201_NAMESPACE - } - } - - /** - * @dev Returns true if the value is in the set. O(1). - */ - function has(MainStorage storage store, ShardID id) internal view returns (bool) { - return store.shards.has(ShardID.unwrap(id)); - } - - /** - * @dev Get or create a value. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ - function getOrAdd(MainStorage storage store, ShardID xCoord) private returns (bool, ShardInfo storage) { - (bool success, StoragePtr ptr) = store.shards.tryAdd(ShardID.unwrap(xCoord)); - return (success, ptr.asShardInfo()); - } - - /** - * @dev Removes a value from a set. O(1). - * - * Reverts if the value does not exist in the set. - */ - function remove(MainStorage storage store, ShardID id) internal { - StoragePtr ptr = store.shards.remove(ShardID.unwrap(id)); - if (ptr.isNull()) { - revert ShardNotExists(id); - } - } - - /** - * @dev Returns the number of values on the set. O(1). - */ - function length(MainStorage storage store) internal view returns (uint256) { - return store.shards.length(); - } - - /** - * @dev Returns the value stored at position `index` in the set. O(1). - * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. - * - * Requirements: - * - * - `index` must be strictly less than {length}. - */ - function at(MainStorage storage store, uint256 index) internal view returns (ShardID, ShardInfo storage) { - (bytes32 xCoord, StoragePtr ptr) = store.shards.at(index); - if (ptr.isNull()) { - revert IndexOutOfBounds(index); - } - return (ShardID.wrap(xCoord), ptr.asShardInfo()); - } - - /** - * @dev Returns the value associated with `key`. O(1). - * - * Requirements: - * - `key` must be in the map. - */ - function get(MainStorage storage store, ShardID key) internal view returns (ShardInfo storage) { - StoragePtr ptr = store.shards.get(ShardID.unwrap(key)); - if (ptr.isNull()) { - revert ShardNotExists(key); - } - return ptr.asShardInfo(); - } - - /** - * @dev Returns the `KeyInfo` associated with `TssKey`. O(1). - * - * Requirements: - * - `key.xCoord` must be in the map. - */ - function get(MainStorage storage store, TssKey calldata key) internal view returns (ShardInfo storage) { - return get(store, ShardID.wrap(bytes32(key.xCoord))); - } - - /** - * @dev Returns the `KeyInfo` associated with `Signature`. O(1). - * - * Requirements: - * - `signature.xCoord` must be in the map. - */ - function get(MainStorage storage store, Signature calldata signature) internal view returns (ShardInfo storage) { - return get(store, ShardID.wrap(bytes32(signature.xCoord))); - } - - /** - * @dev Returns the value associated with `key`. O(1). - */ - function tryGet(MainStorage storage store, ShardID key) private view returns (bool, ShardInfo storage) { - (bool exists, StoragePtr ptr) = store.shards.tryGet(ShardID.unwrap(key)); - return (exists, ptr.asShardInfo()); - } - - /** - * @dev Register a single TSS key. - * Requirements: - * - The `newKey` should not be already registered. - */ - function register(MainStorage storage store, TssKey calldata newKey) internal returns (bool) { - // Check y-parity - require((newKey.yParity == 2 || newKey.yParity == 3), "y parity bit must be 2 or 3, cannot register shard"); - - // Read shard from storage - ShardID id = ShardID.wrap(bytes32(newKey.xCoord)); - (bool created, ShardInfo storage stored) = getOrAdd(store, id); - - // Check if the shard is already registered - if (!created) { - require(stored.nonce == 1 || newKey.yParity == (stored.yParity | 2), "tsskey.yParity mismatch"); - return false; - } - - // Get the current status and nonce - ShardInfo memory shard = stored; - - require( - shard.createdAtBlock == 0 || (shard.yParity | 2) == newKey.yParity, - "the provided y-parity doesn't match the existing y-parity, cannot register shard" - ); - - // Update nonce - shard.nonce |= uint32(BranchlessMath.toUint(shard.nonce == 0)); - - // Save new status and nonce in the storage - stored.createdAtBlock = - BranchlessMath.ternaryU64(shard.createdAtBlock > 0, shard.createdAtBlock, uint64(block.number)); - stored.nonce = shard.nonce; - stored.yParity = newKey.yParity & 1; - return true; - } - - /** - * @dev Register TSS keys in batch. - * Requirements: - * - The `keys` should not be already registered. - */ - function registerTssKeys(MainStorage storage store, TssKey[] calldata keys) internal { - // We don't perform any arithmetic operation, except iterate a loop - unchecked { - // Register or activate tss key (revoked keys keep the previous nonce) - for (uint256 i = 0; i < keys.length; i++) { - register(store, keys[i]); - } - } - } - - /** - * @dev Replace TSS keys in batch. - * Requirements: - * - The `keys` may or may not be registered. - */ - function replaceTssKeys(MainStorage storage store, TssKey[] calldata keys) - internal - returns (TssKey[] memory created, TssKey[] memory revoked) - { - unchecked { - revoked = listShards(store); - created = new TssKey[](keys.length); - - // Make sure the tss keys are correctly ordered, this makes easier to prevent repeated keys, and - // allows binary search. - uint256 createdCount = 0; - for (uint256 i = 0; i < keys.length; i++) { - TssKey calldata key = keys[i]; - if (i > 0) { - TssKey calldata previousKey = keys[i - 1]; - require( - previousKey.xCoord < key.xCoord, "tss keys must be orderd by 'key.xCoord' in asceding order" - ); - } - - if (register(store, key)) { - // Shard registered - created[createdCount++] = TssKey({yParity: key.yParity, xCoord: key.xCoord}); - } else { - // Shard already registered, remove it from the revoke list. - uint256 len = revoked.length; - for (uint256 j = 0; j < len; j++) { - TssKey memory current = revoked[j]; - if (current.xCoord == key.xCoord) { - revoked[j] = revoked[len - 1]; - len--; - assembly { - // decrement list, equivalent to `revoked.length--` - mstore(revoked, len) - } - } - } - } - } - - // Update `created` list length - assembly { - mstore(created, createdCount) - } - - // Revoke Shards - for (uint256 i = 0; i < revoked.length; i++) { - TssKey memory key = revoked[i]; - _revoke(store, ShardID.wrap(bytes32(key.xCoord))); - } - } - } - - /** - * @dev Revoke Shards keys. - * Requirements: - * - The `keys` must be registered. - */ - function revoke(MainStorage storage store, TssKey calldata key) internal returns (bool) { - // Read shard from storage - ShardID id = ShardID.wrap(bytes32(key.xCoord)); - (bool exists, ShardInfo memory stored) = tryGet(store, id); - - if (exists) { - // Check y-parity - require(stored.yParity == (key.yParity & 1), "y parity mismatch, cannot revoke key"); - return _revoke(store, id); - } - return false; - } - - /** - * @dev Revoke Shards keys. - */ - function _revoke(MainStorage storage store, ShardID id) private returns (bool) { - // Remove from the set - StoragePtr ptr = store.shards.remove(ShardID.unwrap(id)); - return !ptr.isNull(); - } - - /** - * @dev Revoke TSS keys im batch. - * Requirements: - * - The `publicKeys` must be registered. - */ - function revokeKeys(MainStorage storage store, TssKey[] calldata publicKeys) - internal - returns (TssKey[] memory revokedKeys) - { - // Revoke tss keys - uint256 keysLength = publicKeys.length; - revokedKeys = new TssKey[](keysLength); - uint256 revokedCount = 0; - - for (uint256 i = 0; i < publicKeys.length; i++) { - if (revoke(store, publicKeys[i])) { - revokedKeys[revokedCount++] = publicKeys[i]; - } - } - - if (revokedKeys.length != keysLength) { - assembly { - mstore(revokedKeys, revokedCount) - } - } - return revokedKeys; - } - - function _t(MainStorage storage store) internal view returns (TssKey[] memory) {} - - /** - * @dev Return all shards registered currently registered. - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - */ - function listShards(MainStorage storage store) internal view returns (TssKey[] memory) { - bytes32[] storage idx = store.shards.keys; - uint256 len = idx.length; - TssKey[] memory shards = new TssKey[](len); - for (uint256 i = 0; i < len; i++) { - ShardID id = ShardID.wrap(idx[i]); - (bool success, ShardInfo storage shard) = tryGet(store, id); - if (!success) { - revert ShardNotExists(id); - } - shards[i] = TssKey(shard.yParity + 2, uint256(ShardID.unwrap(id))); - } - return shards; - } -} diff --git a/analog-gmp/src/utils/BranchlessMath.sol b/analog-gmp/src/utils/BranchlessMath.sol deleted file mode 100644 index 988a1d470..000000000 --- a/analog-gmp/src/utils/BranchlessMath.sol +++ /dev/null @@ -1,456 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (src/utils/BranchlessMath.sol) - -pragma solidity >=0.8.20; - -/** - * Rounding mode used when divide an integer. - */ -enum Rounding { - // Rounds towards zero - Floor, - // Rounds to the nearest value; if the number falls midway, - // it is rounded to the value above. - Nearest, - // Rounds towards positive infinite - Ceil -} - -/** - * @dev Utilities for branchless operations, useful when a constant gas cost is required. - */ -library BranchlessMath { - /** - * @dev Returns the smallest of two numbers. - */ - function min(uint256 x, uint256 y) internal pure returns (uint256) { - return ternary(x < y, x, y); - } - - /** - * @dev Returns the largest of two numbers. - */ - function max(uint256 x, uint256 y) internal pure returns (uint256) { - return ternary(x > y, x, y); - } - - /** - * @dev If `condition` is true returns `a`, otherwise returns `b`. - */ - function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) { - unchecked { - // branchless select, works because: - // b ^ (a ^ b) == a - // b ^ 0 == b - // - // This is better than doing `condition ? a : b` because: - // - Consumes less gas - // - Constant gas cost regardless the inputs - // - Reduces the final bytecode size - return b ^ ((a ^ b) * toUint(condition)); - } - } - - /** - * @dev If `condition` is true returns `a`, otherwise returns `b`. - * see `BranchlessMath.ternary` - */ - function ternary(bool condition, int256 a, int256 b) internal pure returns (int256 r) { - assembly { - r := xor(b, mul(xor(a, b), condition)) - } - } - - /** - * @dev If `condition` is true returns `a`, otherwise returns `b`. - * see `BranchlessMath.ternary` - */ - function ternary(bool condition, address a, address b) internal pure returns (address r) { - assembly { - r := xor(b, mul(xor(a, b), condition)) - } - } - - /** - * @dev If `condition` is true returns `a`, otherwise returns `b`. - * see `BranchlessMath.ternary` - */ - function ternary(bool condition, bytes32 a, bytes32 b) internal pure returns (bytes32 r) { - assembly { - r := xor(b, mul(xor(a, b), condition)) - } - } - - /** - * @dev If `condition` is true returns `a`, otherwise returns `b`. - * see `BranchlessMath.ternary` - */ - function ternaryU128(bool condition, uint128 a, uint128 b) internal pure returns (uint128 r) { - assembly { - r := xor(b, mul(xor(a, b), condition)) - } - } - - /** - * @dev If `condition` is true returns `a`, otherwise returns `b`. - * see `BranchlessMath.ternary` - */ - function ternaryU64(bool condition, uint64 a, uint64 b) internal pure returns (uint64 r) { - assembly { - r := xor(b, mul(xor(a, b), condition)) - } - } - - /** - * @dev If `condition` is true returns `a`, otherwise returns `b`. - * see `BranchlessMath.ternary` - */ - function ternaryU32(bool condition, uint32 a, uint32 b) internal pure returns (uint32 r) { - assembly { - r := xor(b, mul(xor(a, b), condition)) - } - } - - /** - * @dev If `condition` is true returns `a`, otherwise returns `b`. - * see `BranchlessMath.ternary` - */ - function ternaryU8(bool condition, uint8 a, uint8 b) internal pure returns (uint8 r) { - assembly { - r := xor(b, mul(xor(a, b), condition)) - } - } - - /** - * @dev If `condition` is true return `value`, otherwise return zero. - * see `BranchlessMath.ternary` - */ - function selectIf(bool condition, uint256 value) internal pure returns (uint256) { - unchecked { - return value * toUint(condition); - } - } - - /** - * @dev Unsigned saturating addition, bounds to UINT256 MAX instead of overflowing. - * equivalent to: - * uint256 r = x + y; - * return r >= x ? r : UINT256_MAX; - */ - function saturatingAdd(uint256 x, uint256 y) internal pure returns (uint256) { - unchecked { - x = x + y; - y = 0 - toUint(x < y); - return x | y; - } - } - - /** - * @dev Unsigned saturating subtraction, bounds to zero instead of overflowing. - * equivalent to: x > y ? x - y : 0 - */ - function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) { - unchecked { - // equivalent to: a > b ? a - b : 0 - return (a - b) * toUint(a > b); - } - } - - /** - * @dev Unsigned saturating multiplication, bounds to `2 ** 256 - 1` instead of overflowing. - */ - function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) { - unchecked { - uint256 c = a * b; - bool success; - assembly { - // Only true when the multiplication doesn't overflow - // (c / a == b) || (a == 0) - success := or(eq(div(c, a), b), iszero(a)) - } - return c | (toUint(success) - 1); - } - } - - /** - * @dev Unsigned saturating division, bounds to UINT256 MAX instead of overflowing. - */ - function saturatingDiv(uint256 x, uint256 y) internal pure returns (uint256 r) { - assembly { - // Solidity reverts with a division by zero error, while using inline assembly division does - // not revert, it returns zero. - // Reference: https://github.com/ethereum/solidity/issues/15200 - r := div(x, y) - } - } - - /** - * @dev Returns the ceiling of the division of two numbers. - * - * This differs from standard division with `/` in that it rounds towards infinity instead - * of rounding towards zero. - */ - function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { - unchecked { - // The following calculation ensures accurate ceiling division without overflow. - // Since a is non-zero, (a - 1) / b will not overflow. - // The largest possible result occurs when (a - 1) / b is type(uint256).max, - // but the largest value we can obtain is type(uint256).max - 1, which happens - // when a = type(uint256).max and b = 1. - return selectIf(a > 0, ((a - 1) / b + 1)); - } - } - - /** - * @dev Unsigned saturating left shift, bounds to `2 ** 256 - 1` instead of overflowing. - */ - function saturatingShl(uint256 x, uint8 shift) internal pure returns (uint256 r) { - assembly { - // Detect overflow by checking if (x >> (256 - shift)) > 0 - r := gt(shr(sub(256, shift), x), 0) - - // Bounds to `type(uint256).max` if an overflow happened - r := or(shl(shift, x), sub(0, r)) - } - } - - /** - * @dev Returns the absolute unsigned value of a signed value. - * - * Ref: https://graphics.stanford.edu/~seander/bithacks.html#IntegerAbs - */ - function abs(int256 a) internal pure returns (uint256 r) { - assembly { - // Formula from the "Bit Twiddling Hacks" by Sean Eron Anderson. - // Since `n` is a signed integer, the generated bytecode will use the SAR opcode to perform the right shift, - // taking advantage of the most significant (or "sign" bit) in two's complement representation. - // This opcode adds new most significant bits set to the value of the previous most significant bit. As a result, - // the mask will either be `bytes(0)` (if n is positive) or `~bytes32(0)` (if n is negative). - let mask := sar(255, a) - - // A `bytes(0)` mask leaves the input unchanged, while a `~bytes32(0)` mask complements it. - r := xor(add(a, mask), mask) - } - } - - /** - * @dev Computes the absolute difference between x and y. - */ - function absDiff(uint256 x, uint256 y) internal pure returns (uint256) { - return abs(int256(x) - int256(y)); - } - - /** - * @dev Computes the absolute difference between x and y. - */ - function absDiff(int256 x, int256 y) internal pure returns (uint256) { - return abs(x - y); - } - - /** - * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump. - */ - function toUint(bool b) internal pure returns (uint256 u) { - assembly ("memory-safe") { - u := iszero(iszero(b)) - } - } - - /** - * @dev Cast a boolean (false or true) to a int256 (0 or 1) with no jump. - */ - function toInt(bool b) internal pure returns (int256 i) { - assembly ("memory-safe") { - i := iszero(iszero(b)) - } - } - - /** - * @dev Cast an address to uint256 - */ - function toUint(address addr) internal pure returns (uint256) { - return uint256(uint160(addr)); - } - - /** - * @dev Count the consecutive zero bits (trailing) on the right. - */ - function trailingZeros(uint256 x) internal pure returns (uint256 r) { - assembly { - // Compute largest power of two divisor of `x`. - x := and(x, sub(0, x)) - - // Use De Bruijn lookups to convert the power of 2 to log2(x). - // Reference: https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn - r := byte(and(div(80, mod(x, 255)), 31), 0x0706050000040000010003000000000000000000020000000000000000000000) - r := add(byte(31, div(0xf8f0e8e0d8d0c8c0b8b0a8a09890888078706860585048403830282018100800, shr(r, x))), r) - } - } - - /** - * @dev Count the consecutive zero bits (trailing) on the right. - */ - function leadingZeros(uint256 x) internal pure returns (uint256 r) { - return 255 - log2(x) + toUint(x == 0); - } - - /** - * @dev Return the log in base 2 of a positive value rounded towards zero. - * Returns 0 if given 0. - */ - function log2(uint256 x) internal pure returns (uint256 r) { - unchecked { - // Round down to the closest power of 2 - // Reference: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 - x |= x >> 1; - x |= x >> 2; - x |= x >> 4; - x |= x >> 8; - x |= x >> 16; - x |= x >> 32; - x |= x >> 64; - x |= x >> 128; - x = (x >> 1) + 1; - - // Use De Bruijn lookups to convert the power of 2 to floor(log2(x)). - // Reference: https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn - assembly { - r := - byte(and(div(80, mod(x, 255)), 31), 0x0706050000040000010003000000000000000000020000000000000000000000) - r := - add(byte(31, div(0xf8f0e8e0d8d0c8c0b8b0a8a09890888078706860585048403830282018100800, shr(r, x))), r) - } - } - } - - /** - * @dev Aligns `x` to 32 bytes. - */ - function align32(uint256 x) internal pure returns (uint256 r) { - unchecked { - r = saturatingAdd(x, 31) & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0; - } - } - - /** - * @dev Computes `x * 2**exponent`, essentially shifting the value to the left when - * `exp` is positive, or shift to the right when `exp` is negative. - */ - function mul2pow(uint256 x, int256 exponent) internal pure returns (uint256) { - unchecked { - // Rationale: - // - When the exponent is negative, then `x << exp` is zero. - // - When the exponent is positive, then `x >> -exp` is zero. - // Then we can use the `or` operation to get the correct result. - // result = (x << exp) | (x >> -exp) - return (x << uint256(exponent)) | (x >> uint256(-exponent)); - } - } - - /** - * @dev Computes `x * 2**exponent`, bounds to `2 ** 256 - 1` instead overflowing. - */ - function saturatingMul2pow(uint256 x, int256 exponent) internal pure returns (uint256 result) { - unchecked { - result = mul2pow(x, exponent); - // An overflow happens when exponent is positive and (x << exp) >> exp != x. - bool success = (result >> uint256(exponent)) == (x * toUint(exponent > 0)); - // Bounds to `type(uint256).max` if `success` is false. - return result | (toUint(success) - 1); - } - } - - /** - * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. - * Throws if result overflows a uint256 or denominator == 0. - * - * @dev This this an modified version of the original implementation by OpenZeppelin SDK, which is released under MIT. - * original: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.2/contracts/utils/math/Math.sol#L117-L202 - */ - function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { - unchecked { - // Compute remainder. - // - Rounding.Floor then remainder is 0 - // - Rounding.Nearest then remainder is denominator / 2 - // - Rounding.Ceil then remainder is denominator - 1 - uint256 remainder = denominator; - remainder *= toUint(rounding != Rounding.Floor); - remainder >>= toUint(rounding == Rounding.Nearest); - remainder -= toUint(rounding == Rounding.Ceil); - - // 512-bit multiply [prod1 prod0] = x * y + remainder. - // Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use the Chinese Remainder Theorem to reconstruct - // the 512 bit result. The result is stored in two 256 variables such that product = prod1 * 2²⁵⁶ + prod0. - uint256 prod0 = x * y; // Least significant 256 bits of the product - uint256 prod1; // Most significant 256 bits of the product - assembly { - let mm := mulmod(x, y, not(0)) - prod1 := sub(sub(mm, prod0), lt(mm, prod0)) - - // Only round up if the final result is less than 2²⁵⁶. - remainder := mul(remainder, lt(prod1, denominator)) - - // Add 256 bit remainder to 512 bit number. - // Cannot overflow once (2²⁵⁶ - 1)² + 2²⁵⁶ - 1 < 2⁵¹². - mm := add(prod0, remainder) - prod1 := add(prod1, lt(mm, prod0)) - prod0 := mm - } - - // Make sure the result is less than 2**256. Also prevents denominator == 0. - require(prod1 < denominator, "muldiv overflow"); - - /////////////////////////////////////////////// - // 512 by 256 division. - /////////////////////////////////////////////// - - // Make division exact by subtracting the remainder from [prod1 prod0]. - assembly { - // Compute remainder using addmod and mulmod. - remainder := addmod(remainder, mulmod(x, y, denominator), denominator) - - // Subtract 256 bit number from 512 bit number. - prod1 := sub(prod1, gt(remainder, prod0)) - prod0 := sub(prod0, remainder) - } - - // Factor powers of two out of denominator and compute largest power of two divisor of denominator. - // Always >= 1. See https://cs.stackexchange.com/q/138556/92363. - - uint256 twos = denominator & (0 - denominator); - assembly { - // Divide denominator by twos. - denominator := div(denominator, twos) - - // Divide [prod1 prod0] by twos. - prod0 := div(prod0, twos) - - // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one. - twos := add(div(sub(0, twos), twos), 1) - } - - // Shift in bits from prod1 into prod0. - prod0 |= prod1 * twos; - - // Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such - // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for - // four bits. That is, denominator * inv ≡ 1 mod 2⁴. - uint256 inverse = (3 * denominator) ^ 2; - - // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also - // works in modular arithmetic, doubling the correct bits in each step. - inverse *= 2 - denominator * inverse; // inverse mod 2⁸ - inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶ - inverse *= 2 - denominator * inverse; // inverse mod 2³² - inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴ - inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸ - inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶ - - // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. - // This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is - // less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and prod1 - // is no longer required. - return prod0 * inverse; - } - } -} diff --git a/analog-gmp/src/utils/ERC1967.sol b/analog-gmp/src/utils/ERC1967.sol deleted file mode 100644 index 420ef1350..000000000 --- a/analog-gmp/src/utils/ERC1967.sol +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (src/utils/ERC1967.sol) - -pragma solidity >=0.8.0; - -/// @title Minimal implementation of ERC1967 storage slot -library ERC1967 { - /** - * @dev Storage slot with the address of the current implementation. - * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. - */ - // solhint-disable-next-line private-vars-leading-underscore - bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - - /** - * @dev Storage slot with the admin of the contract. - * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1. - */ - // solhint-disable-next-line private-vars-leading-underscore - bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; - - /** - * @dev Emitted when the implementation is upgraded. - */ - event Upgraded(address indexed implementation); - - /** - * @dev Emitted when the admin account has changed. - */ - event AdminChanged(address previousAdmin, address newAdmin); - - /** - * @dev The `implementation` of the proxy is invalid. - */ - error ERC1967InvalidImplementation(address implementation); - - /** - * @dev The `admin` of the proxy is invalid. - */ - error ERC1967InvalidAdmin(address admin); - - /** - * @dev Returns the current admin. - * - * TIP: To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using - * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. - * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` - */ - function getAdmin() internal view returns (address) { - return _getAddressSlot(ADMIN_SLOT); - } - - /** - * @dev Stores a new address in the ERC-1967 admin slot. - */ - function setAdmin(address newAdmin) internal { - if (newAdmin == address(0)) { - revert ERC1967InvalidAdmin(address(0)); - } - emit AdminChanged(getAdmin(), newAdmin); - _setAddressSlot(ADMIN_SLOT, newAdmin); - } - - /** - * @dev Returns the current implementation address. - */ - function getImplementation() internal view returns (address) { - return _getAddressSlot(IMPLEMENTATION_SLOT); - } - - /** - * @dev Stores a new address in the ERC-1967 implementation slot. - */ - function setImplementation(address newImplementation) internal { - if (newImplementation.code.length == 0) { - revert ERC1967InvalidImplementation(newImplementation); - } - emit Upgraded(newImplementation); - _setAddressSlot(IMPLEMENTATION_SLOT, newImplementation); - } - - function _getAddressSlot(bytes32 slot) private view returns (address addr) { - assembly { - addr := sload(slot) - } - } - - function _setAddressSlot(bytes32 slot, address addr) private { - assembly { - sstore(slot, addr) - } - } -} diff --git a/analog-gmp/src/utils/EnumerableSet.sol b/analog-gmp/src/utils/EnumerableSet.sol deleted file mode 100644 index 3055eed88..000000000 --- a/analog-gmp/src/utils/EnumerableSet.sol +++ /dev/null @@ -1,236 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (src/utils/EnumerableMap.sol) -pragma solidity ^0.8.20; - -import {StoragePtr, Pointer} from "./Pointer.sol"; - -/** - * @dev Library for managing an enumerable variant of Solidity's - * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`] - * type. - */ -library EnumerableSet { - using Pointer for StoragePtr; - - error ValueAlreadyPresent(bytes32); - - /** - * @dev Shard info stored in the Gateway Contract - * OBS: the order of the attributes matters! ethereum storage is 256bit aligned, try to keep - * the shard info below 256 bit, so it can be stored in one single storage slot. - * reference: https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html - * - * @custom:storage-location erc7201:analog.one.gateway.shards - */ - struct Map { - bytes32[] keys; - mapping(bytes32 => StoragePtr) values; - } - - /** - * @dev Returns index of a given value in the set. O(1). - * - * Returns -1 if the value is not in the set. - */ - function indexOf(Map storage map, StoragePtr ptr) internal view returns (int256 index) { - assembly ("memory-safe") { - index := not(sload(sub(ptr, 1))) - mstore(0x00, map.slot) - mstore(0x00, sload(add(keccak256(0x00, 0x20), index))) - mstore(0x20, add(map.slot, 1)) - index := or(index, sub(and(eq(ptr, keccak256(0x00, 0x40)), lt(index, sload(map.slot))), 1)) - } - } - - /** - * @dev Returns true if the value is in the set. O(1). - */ - function contains(Map storage map, StoragePtr value) internal view returns (bool r) { - return indexOf(map, value) >= 0; - } - - /** - * @dev Returns true if the key is in the set. O(1). - */ - function has(Map storage map, bytes32 key) internal view returns (bool r) { - assembly ("memory-safe") { - mstore(0x00, key) - mstore(0x20, add(map.slot, 1)) - r := keccak256(0x00, 0x40) - r := gt(sload(sub(r, 1)), 0) - } - } - - /** - * @dev Add a value to a set. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ - function add(Map storage map, bytes32 key) internal returns (StoragePtr r) { - bool success; - (success, r) = tryAdd(map, key); - if (!success) { - revert ValueAlreadyPresent(key); - } - } - - /** - * @dev Add a value to a set. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ - function tryAdd(Map storage map, bytes32 key) internal returns (bool success, StoragePtr r) { - assembly ("memory-safe") { - mstore(0x00, key) - mstore(0x20, add(map.slot, 1)) - r := keccak256(0x00, 0x40) - success := iszero(sload(sub(r, 1))) - if success { - // Load the array size - let size := sload(map.slot) - - // Store the value - mstore(0x00, map.slot) - sstore(add(keccak256(0x00, 0x20), size), key) - - // Update the value's index - sstore(sub(r, 1), not(size)) - - // Update array size - size := add(size, 1) - sstore(map.slot, size) - } - } - } - - /** - * @dev Removes a value from a set. O(1). - * - * Returns the removed value storage pointer, if it was present, or null if it was not. - */ - function remove(Map storage map, bytes32 key) internal returns (StoragePtr r) { - assembly ("memory-safe") { - // Find the value's index - mstore(0x00, key) - mstore(0x20, add(map.slot, 1)) - r := keccak256(0x00, 0x40) - let index := not(sload(sub(r, 1))) - - // First element storage index - let keys_count := sload(map.slot) - mstore(0x00, map.slot) - let keys_start := keccak256(0x00, 0x20) - let val_key_ptr := add(keys_start, index) - - // (index < map.keys.length) && key == map.keys[map.values[key].index] - r := mul(r, and(lt(index, keys_count), eq(key, sload(val_key_ptr)))) - - if r { - // (index + 1) < map.keys.length - if lt(add(index, 1), keys_count) { - // Move the last element to the removed element's position - let last_index := sub(keys_count, 1) - let last_key := sload(add(keys_start, last_index)) - sstore(val_key_ptr, last_key) - - // Update the last element's index - mstore(0x00, last_key) - mstore(0x20, add(map.slot, 1)) - sstore(sub(keccak256(0x00, 0x40), 1), not(index)) - } - - // Update array size - sstore(map.slot, sub(keys_count, 1)) - - // Remove index - sstore(sub(r, 1), 0) - } - } - } - - /** - * @dev Returns the number of values on the set. O(1). - */ - function length(Map storage map) internal view returns (uint256) { - return map.keys.length; - } - - /** - * @dev Returns the value stored at position `index` in the set. O(1). - * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. - * - * Requirements: - * - * - `index` must be strictly less than {length}. - */ - function at(Map storage map, uint256 index) internal view returns (bytes32 key, StoragePtr r) { - assembly ("memory-safe") { - mstore(0x00, map.slot) - key := sload(add(keccak256(0x00, 0x20), index)) - mstore(0x00, key) - mstore(0x20, add(map.slot, 1)) - r := keccak256(0x00, 0x40) - r := mul(r, and(lt(index, sload(map.slot)), eq(index, not(sload(sub(r, 1)))))) - } - } - - /** - * @dev Returns the value associated with `key`. O(1). - * - * Requirements: - * - `key` must be in the map. - */ - function get(Map storage map, bytes32 key) internal view returns (StoragePtr r) { - assembly ("memory-safe") { - mstore(0x00, key) - mstore(0x20, add(map.slot, 1)) - r := keccak256(0x00, 0x40) - r := mul(r, gt(sload(sub(r, 1)), 0)) - } - } - - /** - * @dev Tries to returns the value associated with `key`. O(1). - * Does not revert if `key` is not in the map. - */ - function tryGet(Map storage map, bytes32 key) internal view returns (bool exists, StoragePtr r) { - assembly ("memory-safe") { - mstore(0x00, key) - mstore(0x20, add(map.slot, 1)) - r := keccak256(0x00, 0x40) - exists := gt(sload(sub(r, 1)), 0) - } - } - - /** - * @dev Returns the value associated with `key`. O(1). - */ - function getUnchecked(Map storage map, bytes32 key) internal pure returns (StoragePtr r) { - assembly ("memory-safe") { - mstore(0x00, key) - mstore(0x20, add(map.slot, 1)) - r := keccak256(0x00, 0x40) - } - } - - // /** - // * @dev Return the entire set in an array - // * - // * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - // * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - // * this function has an unbounded cost, and using it as part of a state-changing function may render the function - // * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - // */ - // function _values(EnumerableMap storage m) private view returns (KeyInfo[] memory) { - // ShardID[] memory keys = s.keys; - // KeyInfo[] memory values = new KeyInfo[](keys.length); - // for (uint256 i = 0; i < keys.length; i++) { - // values[i] = s.shards[keys[i]]; - // } - // return values; - // } -} diff --git a/analog-gmp/src/utils/Float9x56.sol b/analog-gmp/src/utils/Float9x56.sol deleted file mode 100644 index 85cea4368..000000000 --- a/analog-gmp/src/utils/Float9x56.sol +++ /dev/null @@ -1,453 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (src/utils/Float9x56.sol) - -pragma solidity >=0.8.0; - -import {BranchlessMath, Rounding} from "./BranchlessMath.sol"; - -/** - * @dev Unsigned Float with 9-bit exponent and 56-bit significand precision (55 explicitly stored). - * - * UFloat9x56 values are described by `2**exponent * (1 + fraction)`, where the `exponent` is a signed - * integer between -255 and 255, and the `fraction` is the next 55 binary digits, which translates to - * 15~17 decimal digits. Zero and values below 2**-255 have a special encoding format. - * - * # Exponent Encoding - * The exponent is encoded using offset-binary representation, with the zero offset being 256, example: - * - 2**-255 is encoded as -255 + 256 == 1. (smallest exponent for normal numbers) - * - 2**0 is encoded as 0 + 256 == 256 (zero offset) - * - 2**6 is encoded as 6 + 256 == 262 - * - 2**255 is encoded as 255 + 256 == 511 (highest exponent) - * - * # Subnormal Numbers - * The smallest possible exponent -256 have a special meaning, it represents subnormal numbers, where the - * exponent is -255 and the +1 is removed, this is useful to represent zero and values below 2**-255. - * - * Assume `e` is an 9-bit encoded exponent between 0~511: - * - When `e > 0`, the number is described by: 2**(e - 256) * (1 + fraction) - * - When `e == 0`, the number is described by: 2**-255 * fraction - */ -type UFloat9x56 is uint64; - -library UFloatMath { - using BranchlessMath for uint256; - - /** - * @dev Constant representing 0.0 in UFloat9x56. - */ - UFloat9x56 internal constant ZERO = UFloat9x56.wrap(0x0000000000000000); - - /** - * @dev Constant representing 1.0 in UFloat9x56. - */ - UFloat9x56 internal constant ONE = UFloat9x56.wrap(0x8000000000000000); - - /** - * @dev Maximum value representable in UFloat9x56, i.e., 2**200 * (2**56 - 1). - */ - UFloat9x56 internal constant MAX = UFloat9x56.wrap(0xffffffffffffffff); - - /** - * @dev Default rounding mode for conversion functions. - */ - Rounding internal constant DEFAULT_ROUNDING = Rounding.Nearest; - - /** - * @dev Number of bits used to represent the mantissa. - */ - uint256 internal constant MANTISSA_DIGITS = 56; - - /** - * @dev Maximum value the mantissa can assume. - */ - uint256 internal constant MANTISSA_MAX = (2 ** MANTISSA_DIGITS) - 1; - - /** - * @dev Minimum value the mantissa can assume, lower than this value is considered a subnormal number. - */ - uint256 internal constant MANTISSA_MIN = 1 << (MANTISSA_DIGITS - 1); - - /** - * @dev The maximum value that can be represented by `UFloat9x56`. - */ - uint256 internal constant MAX_VALUE = MANTISSA_MAX << (256 - MANTISSA_DIGITS); - - /** - * @dev Mask to extract the mantissa from raw `UFloat9x56`. - */ - uint256 private constant MANTISSA_MASK = MANTISSA_MAX >> 1; - - /** - * @dev Bit offset used to extract the exponent from raw `UFloat9x56`. - * This value also represents the number of signficand bits explicitly stored. - */ - uint256 private constant EXPONENT_OFFSET = MANTISSA_DIGITS - 1; - - /** - * @dev Position of the carry bit when converting uint256 to `UFloat9x56`. - */ - uint256 private constant CARRY_BIT = 2 ** (256 - MANTISSA_DIGITS); - - /** - * @dev multiply an UFloat9x56 by an uint256 in constant gas. - */ - function mul(UFloat9x56 x, uint256 y) internal pure returns (uint256 result) { - assembly { - // Extract exponent and fraction - let exponent := shr(55, x) - let fraction := or(and(x, 0x007fffffffffffff), shl(55, gt(exponent, 0))) - exponent := sub(exponent, sub(311, iszero(exponent))) - fraction := shl(mul(exponent, sgt(exponent, 0)), fraction) - - // 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1 - let mm := mulmod(y, fraction, not(0)) - let low := mul(y, fraction) - let high := sub(sub(mm, low), lt(mm, low)) - - // Shift low and high if exponent >= 256 - let shift := mul(sub(0, exponent), slt(exponent, 0)) - mm := gt(shift, 255) - low := xor(low, mul(xor(low, high), mm)) - high := mul(high, iszero(mm)) - - // make sure shift is between 0 and 255 - shift := mod(shift, 256) - - // Combine high and low - high := shl(sub(256, shift), high) - low := shr(shift, low) - result := or(high, low) - } - } - - /** - * @dev Saturating multiplication, bounds to `2 ** 256 - 1` instead of overflowing. - */ - function saturatingMul(UFloat9x56 x, uint256 y) internal pure returns (uint256 result) { - assembly { - // Extract exponent and fraction - let exponent := shr(55, x) - let fraction := or(and(x, 0x007fffffffffffff), shl(55, gt(exponent, 0))) - exponent := sub(exponent, sub(311, iszero(exponent))) - fraction := shl(mul(exponent, sgt(exponent, 0)), fraction) - - // 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1 - let mm := mulmod(y, fraction, not(0)) - let low := mul(y, fraction) - let high := sub(sub(mm, low), lt(mm, low)) - - // Shift low and high if exponent >= 256 - let shift := mul(sub(0, exponent), slt(exponent, 0)) - mm := gt(shift, 255) - low := xor(low, mul(xor(low, high), mm)) - high := mul(high, iszero(mm)) - - // make sure shift is between 0 and 255 - shift := mod(shift, 256) - - // Combine high and low - mm := iszero(shr(shift, high)) // detect overflow - high := shl(sub(256, shift), high) - low := shr(shift, low) - result := or(high, low) - result := or(result, sub(mm, 1)) // saturate if overflow - } - } - - /** - * @dev Returns the mantissa and base 2 exponent as integers, respectively. - * The original number can be recovered by `mantissa * 2 ** exponent`. - * Returns (0, -311) if the value is zero. - */ - function decode(UFloat9x56 value) internal pure returns (uint56, int16) { - unchecked { - // Extract the exponent - int256 exponent = int256(uint256(UFloat9x56.unwrap(value)) >> EXPONENT_OFFSET); - - // Extract the mantissa - uint256 mantissa = uint56(UFloat9x56.unwrap(value) & MANTISSA_MASK); - - // If the value is subnormal, then the exponent is -310 and mantissa msb is not set. - bool isSubnormal = exponent == 0; - mantissa |= BranchlessMath.toUint(!isSubnormal) << EXPONENT_OFFSET; - exponent += BranchlessMath.toInt(isSubnormal && mantissa > 0); - - // Exponent bias + mantissa shift - exponent -= 256 + int256(EXPONENT_OFFSET); - - return (uint56(mantissa), int16(exponent)); - } - } - - /** - * @dev Encode the provided `mantissa` and `exponent` into `UFloat9x56`, this method assumes the - * mantissa msb is set when the number is normal, and unset when the number is subnormal. - */ - function encode(uint256 mantissa, int256 exponent) internal pure returns (UFloat9x56) { - unchecked { - // Minimum exponent is -310 when the mantissa is greater than zero. - int256 minExponent = -311 + BranchlessMath.toInt(mantissa > 0); - require(exponent >= minExponent && exponent <= 200, "UFloat9x56: exponent out of bounds"); - - // If the mantissa is zero, then the exponent must be -311. - exponent = BranchlessMath.ternary(mantissa == 0, -311, exponent); - - // For subnormal numbers the mantissa msb is not set. - bool isSubnormal = mantissa < MANTISSA_MIN && exponent == minExponent; - require( - isSubnormal || (mantissa >= MANTISSA_MIN && mantissa <= MANTISSA_MAX), "UFloat9x56: invalid mantissa" - ); - isSubnormal = isSubnormal && mantissa > 0; - - // Remove mantissa most significant bit. - mantissa &= MANTISSA_MASK; - - // Encode the exponent as an 9-bit unsigned integer. - exponent += 311 - BranchlessMath.toInt(isSubnormal); - - // Shift the exponent to the correct position - exponent <<= EXPONENT_OFFSET; - - // Encode the exponent and mantissa into `UFloat9x56` - return UFloat9x56.wrap(uint64(mantissa) | uint64(uint256(exponent))); - } - } - - /** - * @dev Compare if `UFloat9x56` is equal to another integer, considering only the mantissa bits. - */ - function _integerMask(UFloat9x56 x) private pure returns (uint256, uint256) { - (uint256 mantissa, int256 exponent) = decode(x); - unchecked { - // Shift y if the exponent is negative - mantissa >>= BranchlessMath.abs(exponent) * BranchlessMath.toUint(exponent < 0); - uint256 shift = uint256(exponent) * BranchlessMath.toUint(exponent > 0); - return (type(uint256).max << shift, mantissa << shift); - } - } - - /** - * @dev Compare if `UFloat9x56` is equal to another integer, considering only the mantissa bits. - */ - function eq(UFloat9x56 x, uint256 y) internal pure returns (bool r) { - (uint256 mask, uint256 integer) = _integerMask(x); - return (y & mask) == integer; - } - - /** - * @dev Compare if `UFloat9x56` is equal to another integer, considering only the mantissa bits. - */ - function eq(UFloat9x56 a, UFloat9x56 b) internal pure returns (bool) { - return UFloat9x56.unwrap(a) == UFloat9x56.unwrap(b); - } - - /** - * @dev Compare if `UFloat9x56` is equal to another integer, considering only the mantissa bits. - */ - function gt(UFloat9x56 a, UFloat9x56 b) internal pure returns (bool) { - return UFloat9x56.unwrap(a) > UFloat9x56.unwrap(b); - } - - /** - * @dev Compare if `UFloat9x56` is equal to another integer, considering only the mantissa bits. - */ - function gt(UFloat9x56 x, uint256 y) internal pure returns (bool r) { - return truncate(x) > y; - } - - /** - * @dev Compare if `UFloat9x56` is equal to another integer, considering only the mantissa bits. - */ - function ge(UFloat9x56 a, UFloat9x56 b) internal pure returns (bool) { - return UFloat9x56.unwrap(a) >= UFloat9x56.unwrap(b); - } - - /** - * @dev Compare if `UFloat9x56` is equal to another integer, considering only the mantissa bits. - */ - function lt(UFloat9x56 a, UFloat9x56 b) internal pure returns (bool) { - return UFloat9x56.unwrap(a) < UFloat9x56.unwrap(b); - } - - /** - * @dev Compare if `UFloat9x56` is equal to another integer, considering only the mantissa bits. - */ - function le(UFloat9x56 a, UFloat9x56 b) internal pure returns (bool) { - return UFloat9x56.unwrap(a) <= UFloat9x56.unwrap(b); - } - - /** - * @dev Returns the integer part. This means that non-integer numbers are always truncated towards zero - * This function always returns the precise result. - */ - function truncate(UFloat9x56 value) internal pure returns (uint256) { - (uint256 mantissa, int256 exponent) = decode(value); - return mantissa.mul2pow(exponent); - } - - /** - * @dev Converts uint256 to `UFloat9x56`, following the selected rounding direction. - * By default, it rounds to the nearest value. - */ - function fromUint(uint256 value) internal pure returns (UFloat9x56) { - return fromUint(value, DEFAULT_ROUNDING); - } - - /** - * @dev Converts uint256 to `UFloat9x56`, following the selected rounding direction. - * IMPORTANT: Always round down if the value is greater than `MAX_VALUE`. - */ - function fromUint(uint256 value, Rounding rounding) internal pure returns (UFloat9x56) { - unchecked { - // Compute the exponent, if `value > 0` then the exponent cannot be less than 2**-55. - uint256 exponent = BranchlessMath.log2(value) + 256; - - // Normalize mantissa by removing leading zeros, this step make sure the `CARRY_BIT` - // is always in the same position for any given value. - uint256 mantissa = value << (511 - exponent); - - // Set carry bit based on selected rouding direction. - uint256 carry = CARRY_BIT; - carry *= BranchlessMath.toUint(rounding != Rounding.Floor && value < MAX_VALUE); - carry -= BranchlessMath.toUint(rounding == Rounding.Ceil && carry > 0); - carry >>= BranchlessMath.toUint(rounding == Rounding.Nearest); - carry += mantissa & (CARRY_BIT - 1); - carry = BranchlessMath.toUint(carry >= CARRY_BIT); - - // Shift mantissa to a 56 bit integer then add the carry bit. - mantissa >>= 256 - MANTISSA_DIGITS; - mantissa += carry; - - // Increment the exponent if mantissa overflow after adding the carry bit. - carry = BranchlessMath.toUint(mantissa > MANTISSA_MAX); - mantissa >>= carry; - exponent += carry; - - // If the value is zero, then the exponent must be -311. - exponent *= BranchlessMath.toUint(value > 0); - - // Encode mantissa and exponent into `UFloat9x56` - mantissa &= MANTISSA_MASK; - exponent <<= EXPONENT_OFFSET; - return UFloat9x56.wrap(uint64(mantissa) | uint64(exponent)); - } - } - - /** - * @dev Converts numerator / denominator to `UFloat9x56`, following the selected rounding direction. - */ - function fromRational(uint256 numerator, uint256 denominator) internal pure returns (UFloat9x56) { - return fromRational(numerator, denominator, DEFAULT_ROUNDING); - } - - /** - * @dev Converts numerator / denominator to `UFloat9x56`, following the selected rounding direction. - */ - function fromRational(uint256 numerator, uint256 denominator, Rounding rounding) - internal - pure - returns (UFloat9x56) - { - unchecked { - int256 exponent; - { - // Remove leading zeros from numerator and denominator - uint256 numbits = BranchlessMath.log2(numerator); - uint256 denbits = BranchlessMath.log2(denominator); - numerator <<= 255 - numbits; - denominator <<= 255 - denbits; - - // Compute exponent - exponent = int256(numbits) - int256(denbits); - } - - // If `(numerator / denominator) <= 2**-255` then it is subnormal number - bool isSubnormal = numerator > 0 && exponent <= -255; - - // Adjust the exponent to guarantee the mantissa most significant bit is set - uint256 shift = MANTISSA_DIGITS; - shift -= BranchlessMath.toUint(numerator >= denominator || isSubnormal); - exponent -= int256(shift); - - // Compute (numerator * 2**exponent) / denominator - uint256 mantissa = BranchlessMath.mulDiv(numerator, 1 << shift, denominator, rounding); - - // Adjust mantissa and exponent when it exceeds 56 bits, this is only possible when - // all mantissa bits are set and the value is rounded up, as described below. - // ```solidity - // UFloatMath.fromRational(0x2ffffffffffffff, 3, Rounding.Floor) == (2**0 * 0xffffffffffffff) - // UFloatMath.fromRational(0x2ffffffffffffff, 3, Rounding.Ceil) == (2**1 * 0x80000000000000) - // ``` - shift = BranchlessMath.toUint(mantissa > MANTISSA_MAX); - mantissa >>= shift; - exponent += int256(shift); - - // If the mantissa is zero, then the exponent is the minimum value. - exponent = BranchlessMath.ternary(mantissa == 0, -311, exponent); - - // Adjust exponent to fit in 9 bits - exponent += 311 - int256(BranchlessMath.toUint(isSubnormal)); - exponent <<= EXPONENT_OFFSET; - - // Remove mantissa most significant bit - mantissa &= MANTISSA_MASK; - - return UFloat9x56.wrap(uint64(mantissa) | uint64(uint256(exponent))); - } - } - - /** - * @dev Convert `UFloat9x56` to a rational number, expressed as numerator / denominator. - * Obs: Values above 2**-256 are represented precisely, values below are approximated or round down to zero. - */ - function toRational(UFloat9x56 value) internal pure returns (uint256 numerator, uint256 denominator) { - unchecked { - if (UFloat9x56.unwrap(value) == 0) { - return (0, 1); - } - - int256 exponent; - (numerator, exponent) = decode(value); - - // Remove trailing zeros from mantissa. - { - uint256 trailingZeros = BranchlessMath.trailingZeros(numerator); - trailingZeros *= BranchlessMath.toUint(exponent < 0); - exponent += int256(trailingZeros); - numerator >>= trailingZeros; - } - - if (exponent > 0) { - // The exponent is positive, cannot overflow once the maximum exponent is 200. - // Calculates: (mantissa * 2**exponent) / 1 - numerator <<= uint256(exponent); - denominator = 1; - } else if (exponent > -256) { - // The exponent is negative, so we shift the denominator and keep the numerator. - // Calculates: mantissa / 2**-exponent - denominator = 1 << uint256(-exponent); - } else { - // Is not possible to represent such tiny values accurately given the denominator has more than 256 bit, - // but is still possible to get a good aproximation if we set the numerator to one: - // Calculates: 1 / (2**-exponent / mantissa) - // - // The final exponent is computed as a product of two exponents: - // 2**-exponent == 2**exp0 * 2*exp1 - uint256 exp0 = 255; - uint256 exp1 = BranchlessMath.abs(exponent) - exp0; - - // If numerator is less or equal to `2**exp1`, then the denominator has more than 256bit, so return zero. - if (exp1 >= numerator) { - return (0, 1); - } - - // Compute full 512 bit multiplication and division as (2**exp0 * 2**exp1) / mantissa. - denominator = BranchlessMath.mulDiv(1 << exp0, 1 << exp1, numerator, Rounding.Nearest); - - // Handle the case where the denominator is round towards zero. - numerator = BranchlessMath.toUint(denominator > 0); - denominator |= BranchlessMath.toUint(denominator == 0); - } - } - } -} diff --git a/analog-gmp/src/utils/GasUtils.sol b/analog-gmp/src/utils/GasUtils.sol deleted file mode 100644 index 4a9de9b2e..000000000 --- a/analog-gmp/src/utils/GasUtils.sol +++ /dev/null @@ -1,512 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (src/utils/GasUtils.sol) - -pragma solidity >=0.8.20; - -import {UFloat9x56, UFloatMath} from "./Float9x56.sol"; -import {BranchlessMath} from "./BranchlessMath.sol"; - -/** - * @dev Utilities for compute the GMP gas price, gas cost and gas needed. - */ -library GasUtils { - using BranchlessMath for uint256; - - /** - * @dev How much gas is used until the first `gasleft()` instruction is executed in the `Gateway.batchExecute` method. - * - * HOW TO UPDATE THIS VALUE: - * 1. Run `forge test --match-test=test_gasMeter --fuzz-runs=1 --debug` - * 2. Move the cursor until you enter the `src/Gateway.sol` file. - * 3. Execute the opcodes until you reach the first `GAS` opcode. - * 4. Execute the GAS opcode then copy the `Gas used in call` value to the constant below. - * - * Obs: To guarantee the overhead is constant regardless the input size, always use `calldata` instead of `memory` - * for external functions. - */ - uint256 internal constant BATCH_SELECTOR_OVERHEAD = 465; - - /** - * @dev How much gas is used until the first `gasleft()` instruction is executed. - * - * HOW TO UPDATE THIS VALUE: - * 1. Run `forge test --match-test=test_submitMessageMeter --fuzz-runs=1 --debug` - * 2. Move the cursor until you enter the `src/Gateway.sol` file. - * 3. Execute the opcodes until you reach the first `GAS` opcode. - * 4. Execute the GAS opcode then copy the `Gas used in call` value to the constant below. - * - * Obs: To guarantee the overhead is constant regardless the input size, always use `calldata` instead of `memory` - * for external functions. - */ - uint256 internal constant EXECUTION_SELECTOR_OVERHEAD = 474; - - /** - * @dev Base cost of the `IExecutor.execute` method. - */ - uint256 internal constant EXECUTION_BASE_COST = EXECUTION_SELECTOR_OVERHEAD + 46960; - - /** - * @dev Base cost of the `IGateway.submitMessage` method. - */ - uint256 internal constant SUBMIT_BASE_COST = 24138; - - /** - * @dev Extra gas cost that any account `Contract or EOA` must pay when calling `IGateway.submitMessage` method. - * This cost is necessary for initialize the account's `nonce` storage slot. - */ - uint256 internal constant FIRST_MESSAGE_EXTRA_COST = 17100; - - /** - * @dev Solidity's reserved location for the free memory pointer. - * Reference: https://docs.soliditylang.org/en/v0.8.28/internals/layout_in_memory.html - */ - uint256 internal constant ALLOCATED_MEMORY = 0x40; - - /** - * @dev Read the current allocated size (a.k.a free memory pointer). - */ - function readAllocatedMemory() internal pure returns (uint256 pointer) { - assembly ("memory-safe") { - pointer := mload(ALLOCATED_MEMORY) - } - } - - /** - * @dev Replace the current allocated size by the `newPointer`, and returns the old value stored. - * CAUTION: Only use this method if you know what you are doing. Make sure you don't overwrite any - * memory location that is still in use by the current call context. - */ - function unsafeReplaceAllocatedMemory(uint256 newPointer) internal pure returns (uint256 oldPointer) { - assembly ("memory-safe") { - oldPointer := mload(ALLOCATED_MEMORY) - mstore(ALLOCATED_MEMORY, newPointer) - } - } - - /** - * @dev Compute the gas cost of memory expansion. - * @param words number of words, where a word is 32 bytes - */ - function memoryExpansionGasCost(uint256 words) internal pure returns (uint256) { - unchecked { - return (words.saturatingMul(words) >> 9).saturatingAdd(words.saturatingMul(3)); - } - } - - /** - * @dev Compute the amount of gas used by the `GatewayProxy`. - * @param calldataLen The length of the calldata in bytes - * @param returnLen The length of the return data in bytes - */ - function proxyOverheadGasCost(uint256 calldataLen, uint256 returnLen) internal pure returns (uint256) { - unchecked { - // Convert the calldata and return data length to words - calldataLen = _toWord(calldataLen); - returnLen = _toWord(returnLen); - - // Base cost: OPCODES + COLD SLOAD + COLD DELEGATECALL + RETURNDATACOPY - // uint256 gasCost = 57 + 2100 + 2600; - uint256 gasCost = 31 + 2100 + 2600 + 32; - - // CALLDATACOPY - gasCost = gasCost.saturatingAdd(calldataLen * 3); - - // RETURNDATACOPY - gasCost = gasCost.saturatingAdd(returnLen * 3); - - // MEMORY EXPANSION (minimal 3 due mstore(0x40, 0x80)) - uint256 words = calldataLen.max(returnLen).max(3); - gasCost = gasCost.saturatingAdd(memoryExpansionGasCost(words)); - return gasCost; - } - } - - /** - * @dev Compute the gas cost of the `IGateway.submitMessage` method, without the `GatewayProxy` overhead. - * @param messageSize The size of the message in bytes. This is the `gmp.data.length`. - */ - function _submitMessageGasCost(uint16 messageSize) private pure returns (uint256 gasCost) { - unchecked { - gasCost = SUBMIT_BASE_COST; - - // `countNonZeros` gas cost - uint256 words = (messageSize + 31) >> 5; - gasCost += (words * 106) + (((words + 254) / 255) * 214); - - // CALLDATACOPY - gasCost += words * 3; - - // keccak256 (6 gas per word) - gasCost += words * 6; - - // emit GmpCreated() gas cost (8 gas per byte) - gasCost += words << 8; - - // Memory expansion cost - words += 17 - 1; - gasCost += ((words * words) >> 9) + (words * 3); - - return gasCost; - } - } - - /** - * @dev Compute the gas cost of the `IGateway.submitMessage` method including the `GatewayProxy` overhead. - * @param messageSize The size of the message in bytes. This is the `gmp.data.length`. - */ - function submitMessageGasCost(uint16 messageSize) internal pure returns (uint256 gasCost) { - unchecked { - // Compute the gas cost of the `IGateway.submitMessage` method. - gasCost = _submitMessageGasCost(messageSize); - - // Convert `gmp.data.length` to `abi.encodeCall(IGateway.submitMessage, (...)).length`. - uint256 calldataSize = ((messageSize + 31) & 0xffe0) + 164; - - // Compute the `GatewayProxy` gas overhead. - gasCost += proxyOverheadGasCost(uint16(calldataSize), 32); - - return gasCost; - } - } - - /** - * @dev Compute the minimal gas needed to execute the `IGateway.submitMessage` method. - * @param messageSize The size of the message in bytes. This is the `gmp.data.length`. - */ - function submitMessageGasNeeded(uint16 messageSize) internal pure returns (uint256 gasNeeded) { - unchecked { - // Compute the gas cost of the `IGateway.submitMessage` method. - gasNeeded = _submitMessageGasCost(messageSize); - // gasNeeded = gasNeeded.saturatingSub(2114); - // gasNeeded = _inverseOfAllButOne64th(); - uint256 calldataSize = ((messageSize + 31) & 0xffe0) + 164; - gasNeeded = gasNeeded.saturatingAdd(proxyOverheadGasCost(calldataSize, 32)); - gasNeeded = gasNeeded.saturatingSub(36); - } - } - - /** - * @dev Estimate the price in wei for send an GMP message. - * @param gasPrice The gas price in UFloat9x56 format. - * @param baseFee The base fee in wei. - * @param nonZeros The number of non-zero bytes in the gmp data. - * @param zeros The number of zero bytes in the gmp data. - * @param gasLimit The message gas limit. - */ - function estimateWeiCost(UFloat9x56 gasPrice, uint256 baseFee, uint16 nonZeros, uint16 zeros, uint256 gasLimit) - internal - pure - returns (uint256) - { - // Add execution cost - uint256 gasCost = estimateGas(nonZeros, zeros, gasLimit); - - // Calculate the gas cost: gasPrice * gasCost + baseFee - return UFloatMath.saturatingMul(gasPrice, gasCost).saturatingAdd(baseFee); - } - - /** - * @dev Estimate the gas cost of a GMP message. - * @param dataNonZeros The number of non-zero bytes in the gmp data. - * @param dataZeros The number of zero bytes in the gmp data. - * @param gasLimit The message gas limit. - */ - function estimateGas(uint16 dataNonZeros, uint16 dataZeros, uint256 gasLimit) internal pure returns (uint256) { - uint256 messageSize = uint256(dataNonZeros) + uint256(dataZeros); - unchecked { - // add execution cost - uint256 gasCost = - computeExecutionRefund(uint16(BranchlessMath.min(messageSize, type(uint16).max)), gasLimit); - // add base cost - gasCost = gasCost.saturatingAdd(21000); - - // calldata zero bytes - uint256 zeros = 31 + 30 + 12 + 30 + 31 + 30; - zeros = zeros.saturatingAdd((messageSize.saturatingAdd(31) & 0xffffe0) - uint256(dataZeros)); - gasCost = gasCost.saturatingAdd(zeros.saturatingMul(4)); - - // calldata non-zero bytes - uint256 nonZeros = uint256(dataNonZeros).saturatingAdd(4 + 96 + 1 + 32 + 2 + 20 + 2 + 32 + 32 + 1 + 2); - gasCost = gasCost.saturatingAdd(nonZeros.saturatingMul(16)); - - return gasCost; - } - } - - /** - * @dev Convert byte count to 256bit word count, rounded up. - */ - function _toWord(uint256 byteCount) private pure returns (uint256 words) { - assembly { - words := add(shr(5, byteCount), gt(and(byteCount, 0x1f), 0)) - } - } - - function _debugExecutionGasCost(uint256 messageSize, uint256 gasUsed) internal pure returns (uint256) { - unchecked { - // Selector overhead - // -- First GAS opcode - uint256 baseCost = EXECUTION_SELECTOR_OVERHEAD - 9; - uint256 memoryExpansion = 0x60; - // -- First GAS opcode - - // all opcodes until message.intoCallback() - baseCost += 449; - - // -- message.intoCallback() -- - baseCost += 438; - memoryExpansion = 0x80 + 0x01c4; - uint256 gas = 0; - // CALLDATACOPY 3 + (3 * words) + memory_expansion - baseCost += 3; - gas = _toWord(messageSize) * 3; - memoryExpansion += messageSize; - memoryExpansion = memoryExpansion.align32(); - - // opcodes until keccak256 - baseCost += 31; - - // keccak256 30 + 6 gas per word - baseCost += 30; - gas = gas.saturatingAdd(_toWord(messageSize) * 6); - // - baseCost += 424; - // -- message.intoCallback() -- - - baseCost += 34; - - // -- _verifySignature -- - baseCost += 7933; - // -- _verifySignature -- - - baseCost += 18; - - // _execute - baseCost += 22551; - baseCost += 2; // GAS - - baseCost += 97; - // ------ CALL ------ - - baseCost += 2600; - gas = gas.saturatingAdd(gasUsed); - memoryExpansion = (messageSize.align32() + 0x80 + 0x0120 + 164).align32(); - - // ------ CALL ------ - baseCost += 67; - baseCost += 100; // SLOAD - baseCost += 69; - baseCost += 100; // SSTORE - - // -- emit GmpExecuted -- - baseCost += 141; - memoryExpansion += 0x20; // MSTORE - baseCost += 24; - memoryExpansion += 0x20; // MSTORE - baseCost += 39; - baseCost += 2387; // LOG4 - baseCost += 26; - // -- emit GmpExecuted -- - // end _execute - - baseCost += 34; - - // GasUtils.txBaseCost() - { - baseCost += 64; // base cost - - // chunk start cost - baseCost += 66; - - // Selector + Signature + GmpMessage - uint256 words = messageSize.align32().saturatingAdd(388 + 31) >> 5; - words = (words * 106) + (((words.saturatingSub(255) + 254) / 255) * 214); - gas = gas.saturatingAdd(words); - - baseCost += 171; // End countNonZeros - baseCost += 70; // End txBaseCost - } - // end GasUtils.txBaseCost() - - baseCost += 482; - // ----- GAS ------- - - baseCost += 168; // GAS - baseCost += 6800; // REFUND CALL - baseCost += 184; // RETURN - - gas = gas.saturatingAdd(baseCost); - gas = gas.saturatingAdd(memoryExpansionGasCost(_toWord(memoryExpansion))); - return gas; - } - } - - function _executionGasCost(uint256 messageSize, uint256 gasUsed) internal pure returns (uint256) { - // Safety: The operations below can't overflow because the message size can't be greater than 2**16 - unchecked { - uint256 gas = _toWord(messageSize) * 3; - gas = gas.saturatingAdd(_toWord(messageSize) * 6); - gas = gas.saturatingAdd(gasUsed); - uint256 memoryExpansion = messageSize.align32() + 0x80 + 0x0120 + 164 + 0x40; - { - // Selector + Signature + GmpMessage - uint256 words = messageSize.align32().saturatingAdd(388 + 31) >> 5; - words = (words * 106) + (((words.saturatingSub(255) + 254) / 255) * 214); - gas = gas.saturatingAdd(words); - } - gas = gas.saturatingAdd(EXECUTION_BASE_COST); - gas = gas.saturatingAdd(memoryExpansionGasCost(_toWord(memoryExpansion))); - return gas; - } - } - - /** - * @dev Compute the inverse of `N - floor(N / 64)` defined by EIP-150, used to - * compute the gas needed for a transaction. - */ - function inverseOfAllButOne64th(uint256 x) internal pure returns (uint256 inverse) { - unchecked { - // inverse = (x * 64) / 63 - inverse = x.saturatingShl(6).saturatingDiv(63); - - // Subtract 1 if `inverse` is a multiple of 64 and greater than 0 - inverse -= BranchlessMath.toUint(inverse > 0 && (inverse % 64) == 0); - } - } - - /** - * @dev Compute the gas needed for the transaction, this is different from the gas used. - * `gas needed > gas used` because of EIP-150 - */ - function executionGasNeeded(uint256 messageSize, uint256 gasLimit) internal pure returns (uint256 gasNeeded) { - unchecked { - gasNeeded = _executionGasCost(messageSize, gasLimit); - gasNeeded = gasNeeded.saturatingAdd(2300 - 184); - gasNeeded = inverseOfAllButOne64th(gasNeeded); - messageSize = messageSize.align32().saturatingAdd(388); - gasNeeded = gasNeeded.saturatingAdd(proxyOverheadGasCost(messageSize, 64)); - // Remove the proxy final overhead, once the message requires (2300 - 184) extra gas. - gasNeeded = gasNeeded.saturatingSub(38); - } - } - - /** - * @dev Compute the gas that should be refunded to the executor for the execution. - * @param messageSize The size of the message. - * @param gasUsed The gas used by the gmp message. - */ - function computeExecutionRefund(uint16 messageSize, uint256 gasUsed) - internal - pure - returns (uint256 executionCost) - { - // Add the base `IExecutor.execute` gas cost. - executionCost = _executionGasCost(messageSize, gasUsed); - - // Add `GatewayProxy` gas overhead - unchecked { - // Safety: The operations below can't overflow because the message size can't be greater than 2**16 - uint256 calldataSize = ((uint256(messageSize) + 31) & 0xffffe0) + 388; // selector + Signature + GmpMessage - executionCost = executionCost.saturatingAdd(proxyOverheadGasCost(calldataSize, 64)); - } - } - - /** - * @dev Count the number of non-zero bytes in a byte sequence from memory. - * gas cost = 217 + (words * 112) + ((words - 1) * 193) - */ - function countNonZeros(bytes memory data) internal pure returns (uint256 nonZeros) { - assembly ("memory-safe") { - // Efficient algorithm for counting non-zero bytes in parallel - let size := mload(data) - - // Temporary set the length of the data to zero - mstore(data, 0) - - nonZeros := 0 - for { - // 32 byte aligned pointer, ex: if data.length is 54, then `ptr = data + 32` - let ptr := add(data, and(add(size, 31), 0xffffffe0)) - let end := xor(data, mul(xor(sub(ptr, 480), data), gt(sub(ptr, data), 480))) - } true { end := xor(data, mul(xor(sub(ptr, 480), data), gt(sub(ptr, data), 480))) } { - // Normalize and count non-zero bytes in parallel - let v := 0 - for {} gt(ptr, end) { ptr := sub(ptr, 32) } { - let r := mload(ptr) - r := or(r, shr(4, r)) - r := or(r, shr(2, r)) - r := or(r, shr(1, r)) - r := and(r, 0x0101010101010101010101010101010101010101010101010101010101010101) - v := add(v, r) - } - - // Sum bytes in parallel - v := add(v, shr(128, v)) - v := add(v, shr(64, v)) - v := add(v, shr(32, v)) - v := add(v, shr(16, v)) - v := and(v, 0xffff) - v := add(and(v, 0xff), shr(8, v)) - nonZeros := add(nonZeros, v) - - if eq(ptr, data) { break } - } - - // Restore the original length of the data - mstore(data, size) - } - } - - /** - * @dev Count the number of non-zero bytes from calldata. - * gas cost = 224 + (words * 106) + (((words + 254) / 255) * 214) - */ - function countNonZerosCalldata(bytes calldata data) internal pure returns (uint256 nonZeros) { - assembly ("memory-safe") { - nonZeros := 0 - for { - let ptr := data.offset - let end := add(ptr, data.length) - } lt(ptr, end) {} { - // calculate min(ptr + data.length, ptr + 8160) - let range := add(ptr, 8160) - range := xor(end, mul(xor(range, end), lt(range, end))) - - // Normalize and count non-zero bytes in parallel - let v := 0 - for {} lt(ptr, range) { ptr := add(ptr, 32) } { - let r := calldataload(ptr) - r := or(r, shr(4, r)) - r := or(r, shr(2, r)) - r := or(r, shr(1, r)) - r := and(r, 0x0101010101010101010101010101010101010101010101010101010101010101) - v := add(v, r) - } - - // Sum bytes in parallel - { - let l := and(v, 0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff) - v := shr(8, xor(v, l)) - v := add(v, l) - } - v := add(v, shr(128, v)) - v := add(v, shr(64, v)) - v := add(v, shr(32, v)) - v := add(v, shr(16, v)) - v := and(v, 0xffff) - nonZeros := add(nonZeros, v) - } - } - } - - /** - * @dev Compute the transaction base cost. - */ - function txBaseCost() internal pure returns (uint256) { - unchecked { - uint256 nonZeros = countNonZerosCalldata(msg.data); - uint256 zeros = msg.data.length - nonZeros; - return 21000 + (nonZeros * 16) + (zeros * 4); - } - } -} diff --git a/analog-gmp/src/utils/Hashing.sol b/analog-gmp/src/utils/Hashing.sol deleted file mode 100644 index 408757104..000000000 --- a/analog-gmp/src/utils/Hashing.sol +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (src/utils/Hashing.sol) - -pragma solidity >=0.8.20; - -library Hashing { - /** - * @dev Solidity's reserved location for the free memory pointer. - * Reference: https://docs.soliditylang.org/en/v0.8.28/internals/layout_in_memory.html - */ - uint256 internal constant ALLOCATED_MEMORY = 0x40; - - /** - * @dev Solidity's reserved location for the scratch memory. - * Reference: https://docs.soliditylang.org/en/v0.8.28/internals/layout_in_memory.html - */ - uint256 internal constant SCRATCH_MEMORY = 0x60; - - /** - * @dev Hashes a single 256-bit integer without memory allocation, uses the memory between 0x00~0x20. - */ - function hash(uint256 a) internal pure returns (bytes32 h) { - assembly ("memory-safe") { - mstore(0x00, a) - h := keccak256(0x00, 0x20) - } - } - - /** - * @dev Hashes two 256-bit words without memory allocation, uses the memory between 0x00~0x40. - */ - function hash(uint256 a, uint256 b) internal pure returns (bytes32 h) { - assembly ("memory-safe") { - mstore(0x00, a) - mstore(0x20, b) - h := keccak256(0x00, 0x40) - } - } - - /** - * @dev Hashes three 256-bit words without memory allocation, uses the memory between 0x00~0x60. - * - * The reserverd memory region `0x40~0x60` is restored to its previous state after execution. - * See https://docs.soliditylang.org/en/v0.8.28/internals/layout_in_memory.html for more details. - */ - function hash(uint256 a, uint256 b, uint256 c) internal pure returns (bytes32 h) { - assembly ("memory-safe") { - mstore(0x00, a) - mstore(0x20, b) - - // Backup the free memory pointer - let freeMemBackup := mload(ALLOCATED_MEMORY) - - mstore(ALLOCATED_MEMORY, c) - h := keccak256(0x00, 0x60) - - // Restore the free memory pointer - mstore(ALLOCATED_MEMORY, freeMemBackup) - } - } - - /** - * @dev Hashes four 256-bit words without memory allocation, uses the memory between 0x00~0x80. - * - * The reserverd memory regions `0x40` and `0x60` are saved and restored after the hash is computed. - * See https://docs.soliditylang.org/en/v0.8.28/internals/layout_in_memory.html for more details. - */ - function hash(bytes32 a, bytes32 b, bytes32 c, bytes32 d) internal pure returns (bytes32) { - return hash(uint256(a), uint256(b), uint256(c), uint256(d)); - } - - /** - * @dev Hashes four 256-bit words without memory allocation, uses the memory between 0x00~0x80. - * - * The reserverd memory region `0x40~0x80` is restored to its previous state after execution. - * See https://docs.soliditylang.org/en/v0.8.28/internals/layout_in_memory.html for more details. - */ - function hash(uint256 a, uint256 b, uint256 c, uint256 d) internal pure returns (bytes32 h) { - assembly ("memory-safe") { - mstore(0x00, a) - mstore(0x20, b) - - // Backup the free memory pointer - let freeMemBackup := mload(ALLOCATED_MEMORY) - mstore(ALLOCATED_MEMORY, c) - { - // Backup the scratch space 0x60 - let backup := mload(0x60) - - // Compute the hash - mstore(SCRATCH_MEMORY, d) - h := keccak256(0x00, 0x80) - - // Restore the scratch space 0x60 - mstore(SCRATCH_MEMORY, backup) - } - // Restore the free memory pointer - mstore(ALLOCATED_MEMORY, freeMemBackup) - } - } -} diff --git a/analog-gmp/src/utils/Pointer.sol b/analog-gmp/src/utils/Pointer.sol deleted file mode 100644 index ef92f9a3f..000000000 --- a/analog-gmp/src/utils/Pointer.sol +++ /dev/null @@ -1,253 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (src/utils/StoragePtr.sol) -pragma solidity ^0.8.20; - -/** - * @dev Represents a raw pointer to a value in storage. - */ -type StoragePtr is uint256; - -/** - * @dev Library for reading and writing primitive types to specific storage slots. - * - * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. - * This library helps with reading and writing to such slots without the need for inline assembly. - * - * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. - * - * Example usage to set ERC-1967 implementation slot: - * ```solidity - * contract ERC1967 { - * // Define the slot. Alternatively, use the SlotDerivation library to derive the slot. - * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - * - * function _getImplementation() internal view returns (address) { - * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; - * } - * - * function _setImplementation(address newImplementation) internal { - * require(newImplementation.code.length > 0); - * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; - * } - * } - * ``` - * - * TIP: Consider using this library along with {SlotDerivation}. - */ -library Pointer { - struct AddressSlot { - address value; - } - - struct BooleanSlot { - bool value; - } - - struct Bytes32Slot { - bytes32 value; - } - - struct Uint256Slot { - uint256 value; - } - - struct Int256Slot { - int256 value; - } - - struct StringSlot { - string value; - } - - struct BytesSlot { - bytes value; - } - - /** - * @dev Converts `uint256[] storage` to `StoragePtr`. - */ - function asPtr(uint256[] storage store) internal pure returns (StoragePtr ptr) { - assembly ("memory-safe") { - ptr := store.slot - } - } - - /** - * @dev Converts `bytes32[] storage` to `StoragePtr`. - */ - function asPtr(bytes32[] storage store) internal pure returns (StoragePtr ptr) { - assembly ("memory-safe") { - ptr := store.slot - } - } - - /** - * @dev Converts `bytes storage` to `StoragePtr`. - */ - function asPtr(bytes storage store) internal pure returns (StoragePtr ptr) { - assembly ("memory-safe") { - ptr := store.slot - } - } - - /** - * @dev Wraps a value in a `StoragePtr`. - */ - function asPtr(uint256 value) internal pure returns (StoragePtr) { - return StoragePtr.wrap(value); - } - - /** - * @dev Unwraps a `StoragePtr` to a value. - */ - function asPtr(bytes32 value) internal pure returns (StoragePtr) { - return StoragePtr.wrap(uint256(value)); - } - - /** - * @dev Convert a `StoragePtr` to `uint256`. - */ - function asUint(StoragePtr ptr) internal pure returns (uint256) { - return StoragePtr.unwrap(ptr); - } - - /** - * @dev Convert a `StoragePtr` to `int256`. - */ - function asInt(StoragePtr ptr) internal pure returns (int256) { - return int256(StoragePtr.unwrap(ptr)); - } - - /** - * @dev Convert a `StoragePtr` to `bytes32`. - */ - function asBytes32(StoragePtr ptr) internal pure returns (bytes32) { - return bytes32(StoragePtr.unwrap(ptr)); - } - - /** - * @dev Whether the `StoragePtr` is zero or not. - */ - function isNull(StoragePtr ptr) internal pure returns (bool r) { - assembly ("memory-safe") { - r := iszero(ptr) - } - } - - /** - * @dev Returns an `AddressSlot` with member `value` located at `slot`. - */ - function getAddressSlot(StoragePtr slot) internal pure returns (AddressSlot storage r) { - assembly ("memory-safe") { - r.slot := slot - } - } - - /** - * @dev Converts a `AddressSlot` into an `StoragePtr`. - */ - function asPtr(AddressSlot storage store) internal pure returns (StoragePtr ptr) { - assembly ("memory-safe") { - ptr := store.slot - } - } - - /** - * @dev Returns a `BooleanSlot` with member `value` located at `slot`. - */ - function getBooleanSlot(StoragePtr slot) internal pure returns (BooleanSlot storage r) { - assembly ("memory-safe") { - r.slot := slot - } - } - - /** - * @dev Converts a `BooleanSlot` into an `StoragePtr`. - */ - function asPtr(BooleanSlot storage store) internal pure returns (StoragePtr ptr) { - assembly ("memory-safe") { - ptr := store.slot - } - } - - /** - * @dev Returns a `Bytes32Slot` with member `value` located at `slot`. - */ - function getBytes32Slot(StoragePtr slot) internal pure returns (Bytes32Slot storage r) { - assembly ("memory-safe") { - r.slot := slot - } - } - - /** - * @dev Converts a `Bytes32Slot` into an `StoragePtr`. - */ - function asPtr(Bytes32Slot storage store) internal pure returns (StoragePtr ptr) { - assembly ("memory-safe") { - ptr := store.slot - } - } - - /** - * @dev Returns a `Uint256Slot` with member `value` located at `slot`. - */ - function getUint256Slot(StoragePtr slot) internal pure returns (Uint256Slot storage r) { - assembly ("memory-safe") { - r.slot := slot - } - } - - /** - * @dev Converts a `Uint256Slot` into an `StoragePtr`. - */ - function asPtr(Uint256Slot storage store) internal pure returns (StoragePtr ptr) { - assembly ("memory-safe") { - ptr := store.slot - } - } - - /** - * @dev Returns a `Int256Slot` with member `value` located at `slot`. - */ - function getInt256Slot(StoragePtr slot) internal pure returns (Int256Slot storage r) { - assembly ("memory-safe") { - r.slot := slot - } - } - - /** - * @dev Converts a `Int256Slot` into an `StoragePtr`. - */ - function asPtr(Int256Slot storage store) internal pure returns (StoragePtr ptr) { - assembly ("memory-safe") { - ptr := store.slot - } - } - - /** - * @dev Returns an `StringSlot` representation of the string storage pointer `store`. - */ - function getStringSlot(StoragePtr slot) internal pure returns (StringSlot storage r) { - assembly ("memory-safe") { - r.slot := slot - } - } - - /** - * @dev Returns a `BytesSlot` with member `value` located at `slot`. - */ - function getBytesSlot(StoragePtr slot) internal pure returns (BytesSlot storage r) { - assembly ("memory-safe") { - r.slot := slot - } - } - - /** - * @dev Converts a `BytesSlot` into an `StoragePtr`. - */ - function asPtr(BytesSlot storage store) internal pure returns (StoragePtr ptr) { - assembly ("memory-safe") { - ptr := store.slot - } - } -} diff --git a/analog-gmp/src/utils/Schnorr.sol b/analog-gmp/src/utils/Schnorr.sol deleted file mode 100644 index b72a02c3c..000000000 --- a/analog-gmp/src/utils/Schnorr.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (src/utils/Schnorr.sol) - -pragma solidity >=0.8.20; - -library Schnorr { - // secp256k1 group order - uint256 internal constant Q = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; - - /** - * Verify Schnorr signature (secp256k1) without memory allocation, Solidity's `ecrecover` - * allocates memory, which complicates the gas estimation. - * - * @param parity public key y-coord parity (27 or 28) - * @param px public key x-coord - * @param message 32-byte message hash - * @param e schnorr signature challenge - * @param s schnorr signature - */ - function verify(uint8 parity, uint256 px, uint256 message, uint256 e, uint256 s) - internal - view - returns (bool valid) - { - // the ecrecover precompile implementation checks that the `r` and `s` - // inputs are non-zero (in this case, `px` and `ep`), thus we don't need to - // check if they're zero. - assembly ("memory-safe") { - // backup the memory values for restore later - let b0 := mload(0x40) - let b1 := mload(0x60) - { - // sp = Q - mulmod(s, px, Q) - let sp := sub(Q, mulmod(s, px, Q)) - - // ep = Q - mulmod(e, px, Q) - let ep := sub(Q, mulmod(e, px, Q)) - - // R = ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) - mstore(0x00, sp) - mstore(0x20, parity) - mstore(0x40, px) - mstore(0x60, ep) - pop(staticcall(gas(), 1, 0x00, 0x80, 0x00, 0x20)) - let R := mload(0x00) - - // Compute keccak256(abi.encodePacked(R, parity, px, message)) - mstore(0x20, shl(248, parity)) - mstore(0x21, px) - mstore(0x41, message) - - // sp != 0 && R != 0 - valid := and(gt(sp, 0), gt(R, 0)) - // R == keccak256(abi.encodePacked(R, parity, px, message) - valid := and(valid, eq(e, keccak256(0x0c, 85))) - } - // restore the original memory values - mstore(0x40, b0) - mstore(0x60, b1) - } - } -} diff --git a/analog-gmp/test/Batching.t.sol b/analog-gmp/test/Batching.t.sol deleted file mode 100644 index 4aa7878bf..000000000 --- a/analog-gmp/test/Batching.t.sol +++ /dev/null @@ -1,505 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (test/Batch.t.sol) - -pragma solidity >=0.8.0; - -import {Test, console, Vm} from "forge-std/Test.sol"; -import {VmSafe} from "forge-std/Vm.sol"; -import {FactoryUtils} from "../lib/universal-factory/src/FactoryUtils.sol"; -import {IUniversalFactory} from "../lib/universal-factory/src/IUniversalFactory.sol"; -import {TestUtils, SigningKey, SigningUtils} from "./TestUtils.sol"; -import {GasSpender} from "./utils/GasSpender.sol"; -import {BaseTest} from "./utils/BaseTest.sol"; -import {Gateway, GatewayEIP712} from "../src/Gateway.sol"; -import {GatewayProxy} from "../src/GatewayProxy.sol"; -import {Hashing} from "../src/utils/Hashing.sol"; -import {GasUtils} from "../src/utils/GasUtils.sol"; -import {BranchlessMath} from "../src/utils/BranchlessMath.sol"; -import {UFloat9x56, UFloatMath} from "../src/utils/Float9x56.sol"; -import {IGateway} from "../src/interfaces/IGateway.sol"; -import {IGmpReceiver} from "../src/interfaces/IGmpReceiver.sol"; -import {IExecutor} from "../src/interfaces/IExecutor.sol"; -import { - InboundMessage, - GatewayOp, - Command, - GmpMessage, - GmpCallback, - UpdateKeysMessage, - Signature, - TssKey, - Network, - GmpStatus, - PrimitiveUtils, - GmpSender, - GMP_VERSION -} from "../src/Primitives.sol"; - -contract Batching is BaseTest { - using PrimitiveUtils for UpdateKeysMessage; - using PrimitiveUtils for GmpMessage; - using PrimitiveUtils for GmpSender; - using PrimitiveUtils for address; - using BranchlessMath for uint256; - using SigningUtils for SigningKey; - using FactoryUtils for IUniversalFactory; - - // Chronicle TSS Secret - uint256 private constant SECRET = 0x42; - uint256 private constant SIGNING_NONCE = 0x69; - - // Netowrk ids - uint16 private constant SRC_NETWORK_ID = 1234; - uint16 internal constant DEST_NETWORK_ID = 1337; - - address private constant RECEIVER_CONTRACT = 0x9888d4d78827bE0C2aDeb578019D35b5a36E80a4; - address private constant RECEIVER_CONTRACT_02 = address( - uint160( - uint256( - keccak256( - abi.encodePacked( - uint8(0xff), address(FACTORY), uint256(1), keccak256(type(GasSpender).creationCode) - ) - ) - ) - ) - ); - - // Domain Separators - bytes32 private constant DOMAIN_TYPED_HASH = - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - - bytes32 private constant SRC_DOMAIN_SEPARATOR = keccak256( - abi.encode( - DOMAIN_TYPED_HASH, - keccak256("Analog Gateway Contract"), - keccak256("0.1.0"), - uint256(SRC_NETWORK_ID), - GATEWAY_PROXY - ) - ); - bytes32 private DST_DOMAIN_SEPARATOR = keccak256( - abi.encode( - DOMAIN_TYPED_HASH, - keccak256("Analog Gateway Contract"), - keccak256("0.1.0"), - uint256(DEST_NETWORK_ID), - GATEWAY_PROXY - ) - ); - - address private constant DEPLOYER = 0xbe862AD9AbFe6f22BCb087716c7D89a26051f74C; - uint256 private constant ADMIN_SECRET = 0x955acb49dbb669143455ffbf98e30ae5b2d95343c8b46ce10bf1975d722e8001; - address private constant ADMIN = 0xBf3C099fAAC29F7AF0d883374A790f9b3B06c93A; - - // Gateway Proxy - bytes private constant PROXY_BYTECODE = abi.encodePacked(type(GatewayProxy).creationCode, abi.encode(ADMIN)); - bytes32 private constant PROXY_BYTECODE_HASH = keccak256(PROXY_BYTECODE); - address payable private constant GATEWAY_PROXY = payable( - address( - uint160( - uint256( - keccak256( - abi.encodePacked( - uint8(0xff), - address(FACTORY), - uint256(0), - keccak256(abi.encodePacked(type(GatewayProxy).creationCode, abi.encode(ADMIN))) - ) - ) - ) - ) - ) - ); - - // Gateway Implementation - bytes private constant IMPLEMENTATION_BYTECODE = - abi.encodePacked(type(Gateway).creationCode, abi.encode(DEST_NETWORK_ID, GATEWAY_PROXY)); - bytes32 private constant IMPLEMENTATION_BYTECODE_HASH = keccak256(IMPLEMENTATION_BYTECODE); - address private constant GATEWAY_IMPLEMENTATION = address( - uint160( - uint256( - keccak256(abi.encodePacked(uint8(0xff), address(FACTORY), uint256(0), IMPLEMENTATION_BYTECODE_HASH)) - ) - ) - ); - - bytes32 private constant SIGNING_HASH = keccak256(abi.encode(GATEWAY_PROXY, GATEWAY_IMPLEMENTATION)); - - constructor() { - // Create the Admin account - SigningKey memory signer = TestUtils.createSigner(ADMIN_SECRET); - console.logBytes(abi.encodePacked(signer.pubkey.px, signer.pubkey.py)); - assertEq(ADMIN, signer.addr(), "admin address missmatch"); - - // Deploy `GasSpender` contract - vm.prank(ADMIN, ADMIN); - assertEq( - RECEIVER_CONTRACT, - FACTORY.create3(bytes32(0), type(GasSpender).creationCode), - "GasSpender address missmatch" - ); - - assertEq( - RECEIVER_CONTRACT_02, - FACTORY.create2(bytes32(uint256(1)), type(GasSpender).creationCode), - "GasSpender address missmatch" - ); - - // Deposit funds to the ADMIN account - vm.deal(ADMIN, 100 ether); - } - - function setUp() external { - console.log("-- STEP 01"); - { - // Encode the `IGmpReceiver.onGmpReceived` call - uint256 gasToWaste = 1000; - bytes memory encodedCall = abi.encodeCall( - IGmpReceiver.onGmpReceived, - ( - 0x0000000000000000000000000000000000000000000000000000000000000000, - 1, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0, - abi.encode(gasToWaste) - ) - ); - uint256 gasLimit = TestUtils.calculateBaseCost(encodedCall) + gasToWaste; - TestUtils.executeCall(ADMIN, address(RECEIVER_CONTRACT), gasLimit, 0, encodedCall); - } - console.log("-- STEP 02"); - - SigningKey memory signer = TestUtils.createSigner(SECRET); - vm.deal(DEPLOYER, 100 ether); - vm.startPrank(DEPLOYER, DEPLOYER); - assertEq( - GATEWAY_PROXY, - FACTORY.computeCreate2Address(bytes32(0), PROXY_BYTECODE_HASH), - "GatewayProxy address missmatch" - ); - // bytes memory bytecode = abi.encodePacked(type(Gateway).creationCode, abi.encode(DEST_NETWORK_ID, GATEWAY_PROXY)); - console.log("implementation:", GATEWAY_IMPLEMENTATION); - console.log(" proxy:", GATEWAY_PROXY); - assertEq( - GATEWAY_IMPLEMENTATION, - FACTORY.create2(bytes32(0), IMPLEMENTATION_BYTECODE), - "Gateway implementation address missmatch" - ); - - // 2 - Deploy the Proxy Contract - console.log("-- Deploying GatewayProxy"); - TssKey[] memory keys = new TssKey[](1); - // keys[0] = TssKey({yParity: signer.yParity() == 28 ? 1 : 0, xCoord: signer.xCoord()}); // Shard key - keys[0] = TssKey({yParity: signer.yParity() == 28 ? 3 : 2, xCoord: signer.xCoord()}); // Shard key - Network[] memory networks = new Network[](2); - networks[0].id = SRC_NETWORK_ID; // sepolia network id - networks[0].gateway = GATEWAY_PROXY; // sepolia proxy address - networks[1].id = DEST_NETWORK_ID; // shibuya network id - networks[1].gateway = GATEWAY_PROXY; // shibuya proxy address - bytes memory initializer = abi.encodeCall(Gateway.initialize, (ADMIN, keys, networks)); - // console.logBytes(initializer); - - // vm.startStateDiffRecording(); - { - VmSafe.Wallet memory adminSigner = vm.createWallet(ADMIN_SECRET); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(adminSigner, SIGNING_HASH); - bytes memory authorization = abi.encode(v, r, s, GATEWAY_IMPLEMENTATION); - assertEq( - GATEWAY_PROXY, - FACTORY.create2(bytes32(0), PROXY_BYTECODE, authorization, initializer), - "GatewayProxy address missmatch" - ); - } - - // Deposit funds to the gateway contract - Gateway(GATEWAY_PROXY).deposit{value: 10 ether}(); - } - - function sign(SigningKey memory signer, GmpMessage memory gmp) private pure returns (Signature memory) { - GmpCallback memory callback = gmp.memToCallback(); - (uint256 e, uint256 s) = signer.signPrehashed(callback.eip712hash, SIGNING_NONCE); - return Signature({xCoord: signer.xCoord(), e: e, s: s}); - } - - function computeGmpMessageID(GmpMessage calldata message) external pure returns (bytes32) { - console.logBytes(msg.data); - return message.intoCallback().eip712hash; - } - - function sign(SigningKey memory signer, InboundMessage memory message) - private - view - returns (Signature memory sig) - { - signAt(signer, message, sig); - } - - function computeInboundMessageSigningHash(InboundMessage calldata message) external pure returns (bytes32) { - bytes32 rootHash = bytes32(0); - - GatewayOp[] calldata ops = message.ops; - for (uint256 i = 0; i < ops.length; i++) { - GatewayOp calldata op = ops[i]; - bytes calldata params = op.params; - - bytes32 operationHash; - if (op.command == Command.GMP) { - GmpMessage calldata gmp; - assembly { - gmp := add(params.offset, 0x20) - } - operationHash = gmp.intoCallback().eip712hash; - } else { - TssKey calldata tssKey; - assembly { - tssKey := params.offset - } - operationHash = Hashing.hash(tssKey.yParity, tssKey.xCoord); - } - rootHash = Hashing.hash(uint256(rootHash), uint256(op.command), uint256(operationHash)); - } - rootHash = Hashing.hash(message.version, message.batchID, uint256(rootHash)); - return keccak256( - abi.encodePacked( - "Analog GMP v2", DEST_NETWORK_ID, bytes32(uint256(uint160(address(GATEWAY_PROXY)))), rootHash - ) - ); - } - - function signAt(SigningKey memory signer, InboundMessage memory message, Signature memory sig) private view { - bytes32 signingHash = this.computeInboundMessageSigningHash(message); - (uint256 e, uint256 s) = signer.signPrehashed(signingHash, SIGNING_NONCE); - sig.xCoord = signer.xCoord(); - sig.e = e; - sig.s = s; - } - - function test_gmp_debug() external { - vm.txGasPrice(1); - SigningKey memory signer = TestUtils.createSigner(SECRET); - - // Build and sign GMP message - // bytes memory data = new bytes(3070 + 32); - GmpMessage memory gmp = GmpMessage({ - source: ADMIN.toSender(false), - srcNetwork: SRC_NETWORK_ID, - dest: RECEIVER_CONTRACT, - destNetwork: DEST_NETWORK_ID, - gasLimit: 7845, - nonce: 0, - data: new bytes(800) - }); - { - bytes memory data = gmp.data; - assembly { - mstore(add(data, 0x20), 7845) - } - } - - Signature memory sig = sign(signer, gmp); - - vm.deal(DEPLOYER, 100 ether); - vm.startPrank(DEPLOYER, DEPLOYER); - uint256 gasNeeded = GasUtils.executionGasNeeded(uint16(gmp.data.length), gmp.gasLimit); - uint256 executionCost = GasUtils._executionGasCost(uint16(gmp.data.length), gmp.gasLimit); - emit log_named_uint(" gas needed", gasNeeded); - - require(GATEWAY_PROXY.code.length > 0, "gateway proxy not found"); - Gateway(GATEWAY_PROXY).execute{gas: gasNeeded}(sig, gmp); - - emit log_named_uint(" total cost", GasUtils.computeExecutionRefund(uint16(gmp.data.length), gmp.gasLimit)); - emit log_named_uint("execution cost", executionCost); - } - - function batchExecute(Signature calldata, InboundMessage calldata message) external pure { - console.log("batchExecute:"); - console.logBytes(msg.data); - for (uint256 i = 0; i < message.ops.length; i++) { - console.log("\nop:", i); - GatewayOp calldata op = message.ops[i]; - bytes calldata data = op.params; - console.logBytes(data); - - uint256 offset; - GmpMessage calldata gmp; - assembly { - offset := data.offset - gmp := add(offset, 0x20) - } - data = gmp.data; - - bytes32 value; - assembly { - offset := data.offset - value := calldataload(value) - } - - console.log(offset, vm.toString(value)); - console.logBytes(data); - } - } - - function test_buildBatch() external view { - GatewayOp[] memory ops = new GatewayOp[](2); - ops[0] = GatewayOp({ - command: Command.GMP, - params: abi.encode( - GmpMessage({ - source: GmpSender.wrap(0x7777777777777777777777777777777777777777777777777777777777777777), - srcNetwork: 0x8888, - dest: 0x9999999999999999999999999999999999999999, - destNetwork: 0xAAAA, - gasLimit: 0xBBBBBBBBBBBBBBBB, - nonce: 0xCCCCCCCCCCCCCCCC, - data: abi.encode(uint256(0xDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD)) - }) - ) - }); - // data: hex"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" - - ops[1] = GatewayOp({ - command: Command.GMP, - params: abi.encode( - GmpMessage({ - source: GmpSender.wrap(0x7070707070707070707070707070707070707070707070707070707070707070), - srcNetwork: 0x8080, - dest: 0x9090909090909090909090909090909090909090, - destNetwork: 0xA0A0, - gasLimit: 0xB0B0B0B0B0B0B0B0, - nonce: 0xC0C0C0C0C0C0C0C0, - data: abi.encode(uint256(0xD0D0D0D0D0D0D0D0D0D0D0D0D0D0D0)) - }) - ) - }); - // data: hex"D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0" - - bytes memory b = abi.encodeCall( - Gateway.batchExecute, - ( - Signature({ - xCoord: 0x1111111111111111111111111111111111111111111111111111111111111111, - e: 0x2222222222222222222222222222222222222222222222222222222222222222, - s: 0x3333333333333333333333333333333333333333333333333333333333333333 - }), - InboundMessage({version: 0x44, batchID: 0x5555555555555555, ops: ops}) - ) - ); - console.logBytes(b); - (bool success,) = address(this).staticcall(b); - require(success, "call failed"); - // assertEq(b.length, 1234); - } - - function test_buildBatch2() external view { - SigningKey memory signer = TestUtils.createSigner(SECRET); - - // Build and sign GMP - uint64 gasLimit = 7845; - GatewayOp[] memory ops = new GatewayOp[](1); - ops[0] = GatewayOp({ - command: Command.GMP, - params: abi.encode( - GmpMessage({ - source: DEPLOYER.toSender(false), - srcNetwork: SRC_NETWORK_ID, - dest: RECEIVER_CONTRACT, - destNetwork: DEST_NETWORK_ID, - gasLimit: gasLimit, - nonce: 0, - data: abi.encode(gasLimit) - }) - ) - }); - Signature memory sig = Signature({xCoord: 0, e: 0, s: 0}); - InboundMessage memory inbound = - InboundMessage({version: 1, batchID: uint64(uint256(keccak256("some batch"))), ops: ops}); - - // console.log("will sign..."); - signAt(signer, inbound, sig); - bytes memory b = abi.encodeCall(Gateway.batchExecute, (sig, inbound)); - console.logBytes(b); - (bool success,) = address(this).staticcall(b); - require(success, "call failed"); - // assertEq(b.length, 1234); - } - - function test_batch_debug() external { - vm.txGasPrice(1); - SigningKey memory signer = TestUtils.createSigner(SECRET); - vm.deal(signer.addr(), 100 ether); - uint64 gasLimit = 7845; - - ///////////////////// - // Build the batch // - ///////////////////// - GatewayOp[] memory ops = new GatewayOp[](2); - ops[0] = GatewayOp({ - command: Command.GMP, - params: abi.encode( - GmpMessage({ - source: DEPLOYER.toSender(false), - srcNetwork: SRC_NETWORK_ID, - dest: RECEIVER_CONTRACT, - destNetwork: DEST_NETWORK_ID, - gasLimit: gasLimit, - nonce: 0, - data: abi.encode(gasLimit) - }) - ) - }); - ops[1] = GatewayOp({ - command: Command.GMP, - params: abi.encode( - GmpMessage({ - source: DEPLOYER.toSender(false), - srcNetwork: SRC_NETWORK_ID, - dest: RECEIVER_CONTRACT_02, - destNetwork: DEST_NETWORK_ID, - gasLimit: gasLimit, - nonce: 0, - data: abi.encode(gasLimit) - }) - ) - }); - InboundMessage memory inbound = - InboundMessage({version: 1, batchID: uint64(uint256(keccak256("some batch"))), ops: ops}); - - //////////////////// - // Sign the batch // - //////////////////// - Signature memory sig = sign(signer, inbound); - bytes memory encodedCall = abi.encodeCall(Gateway.batchExecute, (sig, inbound)); - - console.log("encoded call:"); - console.logBytes(encodedCall); - - // vm.deal(DEPLOYER, 100 ether); - // vm.startPrank(DEPLOYER, DEPLOYER); - uint256 gasNeeded = GasUtils.executionGasNeeded(uint16(32), gasLimit); - uint256 executionCost = GasUtils._executionGasCost(uint16(32), gasLimit); - emit log_named_uint(" gas needed", gasNeeded); - emit log_named_uint("execution cost", executionCost); - require(GATEWAY_PROXY.code.length > 0, "gateway proxy not found"); - - uint256 baseCost; - bool success; - bytes memory result; - // (executionCost, baseCost, success, result) = address(GATEWAY_PROXY).call(encodedCall); - console.log("will execute.."); - (executionCost, baseCost, success, result) = - TestUtils.tryExecuteCall(signer.addr(), GATEWAY_PROXY, 500_000, 0, encodedCall); - emit log_named_uint("execution cost", executionCost); - emit log_named_uint(" base cost", baseCost); - if (!success) { - console.log("reverted:"); - console.logBytes(result); - assembly { - revert(add(result, 0x20), mload(result)) - } - } - - emit log_named_uint(" total cost", GasUtils.computeExecutionRefund(uint16(32), gasLimit)); - emit log_named_uint("execution cost", executionCost); - } -} diff --git a/analog-gmp/test/EnumerableSet.t.sol b/analog-gmp/test/EnumerableSet.t.sol deleted file mode 100644 index ebdeb7d28..000000000 --- a/analog-gmp/test/EnumerableSet.t.sol +++ /dev/null @@ -1,208 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (test/EnumerableSet.t.sol) - -pragma solidity >=0.8.0; - -import {Test, console} from "forge-std/Test.sol"; -import {TestUtils} from "./TestUtils.sol"; -import {EnumerableSet, StoragePtr} from "../src/utils/EnumerableSet.sol"; -import {StoragePtr, Pointer} from "../src/utils/Pointer.sol"; -import {BranchlessMath} from "../src/utils/BranchlessMath.sol"; - -contract EnumerableSetTest is Test { - using BranchlessMath for uint256; - using EnumerableSet for EnumerableSet.Map; - using Pointer for StoragePtr; - using Pointer for uint256; - using Pointer for Pointer.Uint256Slot; - - uint256 private constant ITERATIONS = 10; - - EnumerableSet.Map private map; - - struct MyStruct { - uint256 a; - uint256 b; - uint256 c; - } - - function _add(uint256 key, uint256 value, bool success) private returns (MyStruct storage r) { - bytes32 ptr = map.add(bytes32(key)).asBytes32(); - if (success) { - assertNotEq(ptr, bytes32(0), "map.add failed"); - assembly { - r.slot := ptr - } - r.a = value; - r.b = value + 1; - r.c = value + 2; - } else { - assertEq(ptr, bytes32(0), "expect map.add to fail"); - assembly { - r.slot := 0 - } - } - } - - function _at(uint256 index, bool success) private view returns (MyStruct storage r) { - (, StoragePtr raw) = map.at(index); - bytes32 ptr = raw.asBytes32(); - if (success) { - assertNotEq(ptr, bytes32(0), "map.at failed"); - assembly { - r.slot := ptr - } - } else { - assertEq(ptr, bytes32(0), "expect map.at to fail"); - assembly { - r.slot := 0 - } - } - } - - function _get(uint256 key, bool success) private view returns (MyStruct storage r) { - bytes32 ptr = map.get(bytes32(key)).asBytes32(); - if (success) { - assertNotEq(ptr, bytes32(0), "map.at failed"); - assembly { - r.slot := ptr - } - } else { - assertEq(ptr, bytes32(0), "expect map.at to fail"); - assembly { - r.slot := 0 - } - } - } - - function _removeByKey(uint256 key, bool success) private returns (MyStruct storage r) { - bytes32 ptr = map.remove(bytes32(key)).asBytes32(); - if (success) { - assertNotEq(ptr, bytes32(0), "map.at failed"); - assembly { - r.slot := ptr - } - } else { - assertEq(ptr, bytes32(0), "expect map.at to fail"); - assembly { - r.slot := 0 - } - } - } - - /** - * Test if `Map.add` and `Map.at` work as expected. - */ - function test_add() external { - assertEq(map.length(), 0, "Map should be empty"); - - MyStruct storage s; - for (uint256 i = 0; i < ITERATIONS; i++) { - s = _add(0x1234 + i, i + 1, true); - assertEq(map.length(), i + 1); - for (uint256 j = 0; j < ITERATIONS; j++) { - s = _at(j, j <= i); - if (j <= i) { - assertEq(s.a, j + 1, "MyStruct.a mismatch"); - assertEq(s.b, j + 2, "MyStruct.b mismatch"); - assertEq(s.c, j + 3, "MyStruct.c mismatch"); - } - } - } - } - - /** - * Test if `Map.add` and `Map.at` work as expected. - */ - function test_remove() external { - assertEq(map.length(), 0, "Map should be empty"); - - MyStruct storage s; - for (uint256 i = 0; i < ITERATIONS; i++) { - s = _add(0xdeadbeef + i, i + 1, true); - } - - assertEq(map.length(), ITERATIONS, "unexpected map length"); - uint256 count = ITERATIONS - 1; - _removeByKey(0xdeadbeef + count, true); - assertEq(map.length(), count, "element not removed"); - - // Cannot remove the same key twice - _removeByKey(0xdeadbeef + count, false); - - // Cannot remove an unknown key - _removeByKey(0xdeadbeef + ITERATIONS * 2, false); - - for (uint256 i = 0; i < ITERATIONS; i++) { - s = _at(i, i < count); - if (i < count) { - assertEq(s.a, i + 1, "MyStruct.a mismatch"); - assertEq(s.b, i + 2, "MyStruct.b mismatch"); - assertEq(s.c, i + 3, "MyStruct.c mismatch"); - } - } - - uint256 removeIndex = count - 3; - _removeByKey(0xdeadbeef + removeIndex, true); - count -= 1; - assertEq(map.length(), count, "element not removed"); - s = _at(removeIndex, true); - assertEq(s.a, count + 1, "MyStruct.a mismatch"); - assertEq(s.b, count + 2, "MyStruct.b mismatch"); - assertEq(s.c, count + 3, "MyStruct.c mismatch"); - - s = _at(removeIndex + 1, true); - assertEq(s.a, removeIndex + 2, "MyStruct.a mismatch"); - assertEq(s.b, removeIndex + 3, "MyStruct.b mismatch"); - assertEq(s.c, removeIndex + 4, "MyStruct.c mismatch"); - - for (uint256 i = 0; i < map.keys.length; i++) { - uint256 key = uint256(map.keys[i]); - assertEq(map.values[bytes32(key)].asUint(), key - 0xdeadbeef + 1, "MyStruct.a mismatch"); - s = _get(key, true); - assertEq(s.a, key - 0xdeadbeef + 1, "MyStruct.a mismatch"); - assertEq(s.b, key - 0xdeadbeef + 2, "MyStruct.b mismatch"); - assertEq(s.c, key - 0xdeadbeef + 3, "MyStruct.c mismatch"); - } - } - - /** - * Test if `Map.add` and `Map.at` work as expected. - */ - function test_fuzz(bytes32 key, uint256 value) external { - assertEq(map.length(), 0, "Map should be empty"); - - // Map.length works - Pointer.Uint256Slot storage store; - store = map.add(key).getUint256Slot(); - assertFalse(store.asPtr().isNull(), "invalid pointer"); - store.value = value; - - // Map.length works - assertEq(map.length(), 1, "unexpected map length"); - - // Map.get works - store = map.get(key).getUint256Slot(); - assertEq(store.value, value, "unexpected value when retrieving by key"); - - // Map.indexOf works - int256 index = map.indexOf(store.asPtr()); - assertEq(index, 0, "unexpected index"); - - // Map.at works - (bytes32 atKey, StoragePtr raw) = map.at(0); - store = raw.getUint256Slot(); - assertEq(store.value, value, "unexpected value when retrieving by index"); - assertEq(atKey, key, "unexpected key when retrieving by index"); - - // Map.contains works - StoragePtr ptr = map.get(key); - assertTrue(map.contains(ptr), "the key should be in the map"); - - // Map.contains returns false for invalid pointers - ptr = (ptr.asUint() + 1).asPtr(); - assertFalse(map.contains(ptr), "invalid pointer"); - ptr = (ptr.asUint() - 2).asPtr(); - assertFalse(map.contains(ptr), "invalid pointer"); - } -} diff --git a/analog-gmp/test/Example.t.sol b/analog-gmp/test/Example.t.sol deleted file mode 100644 index 0b196058e..000000000 --- a/analog-gmp/test/Example.t.sol +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (test/Example.t.sol) - -pragma solidity >=0.8.0; - -import {Test} from "forge-std/Test.sol"; -import {VmSafe} from "forge-std/Vm.sol"; -import {console} from "forge-std/console.sol"; -import {Random} from "./Random.sol"; -import {MockERC20} from "./MockERC20.sol"; -import {GmpTestTools} from "./GmpTestTools.sol"; -import {TestUtils, SigningKey, VerifyingKey, SigningUtils, VerifyingUtils} from "./TestUtils.sol"; -import {Gateway, GatewayEIP712} from "../src/Gateway.sol"; -import {GatewayProxy} from "../src/GatewayProxy.sol"; -import {IGateway} from "../src/interfaces/IGateway.sol"; -import {IGmpReceiver} from "../src/interfaces/IGmpReceiver.sol"; -import {IExecutor} from "../src/interfaces/IExecutor.sol"; -import {GasUtils} from "../src/utils/GasUtils.sol"; -import { - GmpMessage, - UpdateKeysMessage, - Signature, - TssKey, - Network, - GmpStatus, - GmpSender, - PrimitiveUtils -} from "../src/Primitives.sol"; - -contract ExampleTest is Test { - using SigningUtils for SigningKey; - using VerifyingUtils for VerifyingKey; - using PrimitiveUtils for GmpMessage; - using PrimitiveUtils for address; - - uint16 private constant SRC_NETWORK_ID = 1234; - uint16 private constant DEST_NETWORK_ID = 1337; - uint256 private constant SENDER_SECRET = uint248(uint256(keccak256("secret"))); - address private _sender; - - address private constant ALICE = address(bytes20(keccak256("Alice"))); - address private constant BOB = address(bytes20(keccak256("Bob"))); - - function setUp() external { - vm.deal(ALICE, 100 ether); - vm.deal(BOB, 100 ether); - } - - function deployGateway(VmSafe.Wallet memory admin, SigningKey memory signer, uint16[] memory networkIds) - private - returns (Network[] memory networks) - { - TssKey[] memory keys = new TssKey[](1); - keys[0] = TssKey({yParity: signer.pubkey.yParity() == 28 ? 3 : 2, xCoord: signer.pubkey.px}); - - networks = new Network[](networkIds.length); - for (uint256 i = 0; i < networks.length; i++) { - networks[i].id = networkIds[i]; - networks[i].gateway = TestUtils.computeGatewayProxyAddress(admin.addr, bytes32(uint256(networks[i].id))); - vm.deal(networks[i].gateway, 100 ether); - } - - // bytes memory initializer = abi.encodeCall(Gateway.initialize, (msg.sender, keys, networks)); - for (uint256 i = 0; i < networks.length; i++) { - address proxy = - address(TestUtils.setupGateway(admin, bytes32(uint256(networks[i].id)), networks[i].id, keys, networks)); - assertEq(proxy, networks[i].gateway, "GatewayProxy address mismatch"); - vm.deal(proxy, 100 ether); - } - } - - function testSignature() external pure { - SigningKey memory sk = TestUtils.createSigner(); - VerifyingKey memory vk = sk.pubkey; - (uint256 c, uint256 z) = sk.sign("hello world!", Random.nextUint()); - assertTrue(vk.verify("hello world!", c, z), "invalid signature"); - } - - function testTeleportTokens() external { - vm.txGasPrice(1); - VmSafe.Wallet memory senderWallet = vm.createWallet(SENDER_SECRET); - _sender = senderWallet.addr; - vm.deal(_sender, 100 ether); - - // Step 1: Deploy the Gateway contract - SigningKey memory signer = TestUtils.createSigner(); - Gateway srcGateway; - Gateway dstGateway; - { - uint16[] memory networkIds = new uint16[](2); - networkIds[0] = SRC_NETWORK_ID; - networkIds[1] = DEST_NETWORK_ID; - Network[] memory networks = deployGateway(senderWallet, signer, networkIds); - srcGateway = Gateway(payable(networks[0].gateway)); - dstGateway = Gateway(payable(networks[1].gateway)); - } - - // Step 2: Deploy the sender and recipient contracts - vm.startPrank(_sender, _sender); - MockERC20 srcToken = MockERC20(vm.computeCreateAddress(_sender, vm.getNonce(_sender) + 1)); - MockERC20 dstToken = - new MockERC20("Destination Token", "B", dstGateway, srcToken, srcGateway.networkId(), ALICE, 0); - srcToken = new MockERC20("Source Token", "A", srcGateway, dstToken, dstGateway.networkId(), ALICE, 1000); - - // Step 3: Send GMP message - GmpSender source = address(srcToken).toSender(false); - GmpMessage memory gmp = GmpMessage({ - source: source, - srcNetwork: SRC_NETWORK_ID, - dest: address(dstToken), - destNetwork: DEST_NETWORK_ID, - gasLimit: 100_000, - nonce: 0, - data: abi.encode(MockERC20.CrossChainTransfer({from: ALICE, to: BOB, amount: 100})) - }); - - // Calculate the expect GMP gas cost - uint256 gasCost; - { - uint256 nonZeros = GasUtils.countNonZeros(gmp.data); - uint256 zeros = gmp.data.length - nonZeros; - gasCost = GasUtils.estimateGas(uint16(nonZeros), uint16(zeros), gmp.gasLimit); - } - - // Expect `GmpCreated` to be emitted - bytes32 messageID = gmp.eip712hash(); - vm.expectEmit(true, true, true, true, address(srcGateway)); - emit IGateway.GmpCreated( - messageID, - GmpSender.unwrap(gmp.source), - gmp.dest, - gmp.destNetwork, - gmp.gasLimit, - uint64(gasCost), - gmp.nonce, - gmp.data - ); - - { - // Estimate the cost of teleporting 100 tokens - uint256 gmpCost = srcToken.teleportCost(); - - // Submit the GMP message from `sender` contract - vm.stopPrank(); - vm.prank(ALICE, ALICE); - srcToken.teleport{value: gmpCost}(BOB, 100); - } - - vm.startPrank(_sender, _sender); - (uint256 c, uint256 z) = signer.signPrehashed(messageID, Random.nextUint()); - Signature memory sig = Signature({xCoord: signer.pubkey.px, e: c, s: z}); - assertTrue(dstGateway.gmpInfo(messageID).status == GmpStatus.NOT_FOUND, "GMP message already executed"); - - // Expect `GmpExecuted` to be emitted - vm.expectEmit(true, true, true, true, address(dstGateway)); - emit IExecutor.GmpExecuted(messageID, gmp.source, gmp.dest, GmpStatus.SUCCESS, messageID); - - // Execute the GMP message - dstGateway.execute(sig, gmp); - assertTrue(dstGateway.gmpInfo(messageID).status == GmpStatus.SUCCESS, "failed to execute GMP message"); - - // Check balance - assertEq(srcToken.balanceOf(ALICE), 900, "sender balance mismatch"); - assertEq(dstToken.balanceOf(ALICE), 0, "recipient balance mismatch"); - assertEq(srcToken.balanceOf(BOB), 0, "sender balance mismatch"); - assertEq(dstToken.balanceOf(BOB), 100, "recipient balance mismatch"); - } -} diff --git a/analog-gmp/test/Float9x56.t.sol b/analog-gmp/test/Float9x56.t.sol deleted file mode 100644 index e1586b167..000000000 --- a/analog-gmp/test/Float9x56.t.sol +++ /dev/null @@ -1,367 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (test/Float9x56.t.sol) - -pragma solidity >=0.8.0; - -import {Test, console} from "forge-std/Test.sol"; -import {BranchlessMath, Rounding} from "../src/utils/BranchlessMath.sol"; -import {UFloat9x56, UFloatMath} from "../src/utils/Float9x56.sol"; - -contract UFloatMathMock { - function encode(uint256 mantissa, int256 exponent) external pure returns (UFloat9x56) { - return UFloatMath.encode(mantissa, exponent); - } -} - -contract UFloatMathTest is Test { - using UFloatMath for UFloat9x56; - using BranchlessMath for uint256; - - UFloatMathMock mock; - - // Fuzz test fixtures for mantissa, see: - // - https://book.getfoundry.sh/forge/fuzz-testing#fuzz-test-fixtures - uint56[] public fixtureMantissa = [0, 1, uint56(UFloatMath.MANTISSA_MIN), uint56(UFloatMath.MANTISSA_MAX)]; - - constructor() { - mock = new UFloatMathMock(); - } - - /** - * @dev multiply an UFloat9x56 by an uint256 in constant gas. - */ - function test_fuzzMul(uint256 x) external pure { - // Any value multiplied by 0 should be 0 - uint256 result = UFloatMath.ZERO.mul(x); - assertEq(result, 0); - - // Any value times 1 should be the same value - result = UFloatMath.ONE.mul(x); - assertEq(result, x); - - // Any value times 2 should be the double - result = UFloatMath.fromUint(2).mul(x); - unchecked { - assertEq(result, x << 1); - } - } - - function test_mul() external pure { - unchecked { - // Any value multiplied by 0 should be 0 - UFloat9x56 value = UFloatMath.ZERO; - assertEq(value.mul(0), 0); - assertEq(value.mul(1), 0); - assertEq(value.mul(type(uint256).max), 0); - - // Any value multiplied 1 should be the same value - value = UFloatMath.ONE; - assertEq(value.mul(0), 0); - assertEq(value.mul(1), 1); - assertEq(value.mul(type(uint256).max), type(uint256).max); - - // Any value times 2 should be the double - value = UFloatMath.fromUint(2); - assertEq(value.mul(0), 0); - assertEq(value.mul(1), 2); - assertEq(value.mul(type(uint256).max), type(uint256).max * 2); - - // Test fractions - value = UFloatMath.fromRational(1, 2, Rounding.Floor); - assertEq(value.mul(0), 0); - assertEq(value.mul(1), 0); - assertEq(value.mul(2), 1); - assertEq(value.mul(type(uint256).max), type(uint256).max / 2); - } - } - - function test_eq() external pure { - unchecked { - assertTrue(UFloatMath.ZERO.eq(0)); - assertFalse(UFloatMath.ZERO.eq(1)); - assertFalse(UFloatMath.ZERO.eq(type(uint256).max)); - - assertFalse(UFloatMath.ONE.eq(0)); - assertTrue(UFloatMath.ONE.eq(1)); - assertFalse(UFloatMath.ONE.eq(type(uint256).max)); - - UFloat9x56 value = UFloatMath.fromUint(type(uint256).max); - assertFalse(value.eq(0)); - assertFalse(value.eq(1)); - assertFalse(value.eq(type(uint256).max / 2)); - assertTrue(value.eq(type(uint256).max)); - - value = UFloatMath.fromRational(123456789, 1111); - assertFalse(value.eq(0)); - assertFalse(value.eq(1)); - assertFalse(value.eq(type(uint256).max)); - assertTrue(value.eq(uint256(123456789) / uint256(1111))); - } - } - - /** - * @dev Saturating multiplication, bounds to `2 ** 256 - 1` instead of overflowing. - */ - function test_saturatingMul() external pure { - unchecked { - // Any value multiplied by 0 should be 0 - UFloat9x56 value = UFloatMath.ZERO; - assertEq(value.saturatingMul(0), 0); - assertEq(value.saturatingMul(1), 0); - assertEq(value.saturatingMul(type(uint256).max), 0); - - // Any value multiplied 1 should be the same value - value = UFloatMath.ONE; - assertEq(value.saturatingMul(0), 0); - assertEq(value.saturatingMul(1), 1); - assertEq(value.saturatingMul(type(uint256).max), type(uint256).max); - - // Any value times 2 should be the double or bounded to 2 ** 256 - 1 - value = UFloatMath.fromUint(2); - assertEq(value.saturatingMul(0), 0); - assertEq(value.saturatingMul(1), 2); - assertEq(value.saturatingMul(type(uint256).max - 1), type(uint256).max); - assertEq(value.saturatingMul(type(uint256).max), type(uint256).max); - - // Test fractions - value = UFloatMath.fromRational(1, 2, Rounding.Floor); - assertEq(value.saturatingMul(0), 0); - assertEq(value.saturatingMul(1), 0); - assertEq(value.saturatingMul(2), 1); - assertEq(value.saturatingMul(type(uint256).max), type(uint256).max / 2); - } - } - - /** - * @dev Returns the mantissa and base 2 exponent as integers, respectively. - * The original number can be recovered by `mantissa * 2 ** exponent`. - * Returns (0, -311) if the value is zero. - */ - function test_decode() external pure { - (uint256 mantissa, int256 exponent) = UFloatMath.ZERO.decode(); - assertEq(mantissa, 0); - assertEq(exponent, -311); - - (mantissa, exponent) = UFloatMath.ONE.decode(); - assertEq(mantissa, UFloatMath.MANTISSA_MIN); - assertEq(exponent, -55); - - (mantissa, exponent) = UFloatMath.MAX.decode(); - assertEq(mantissa, 0xffffffffffffff); - assertEq(exponent, 200); - - (mantissa, exponent) = UFloatMath.fromRational(1, 3).decode(); - assertEq(mantissa, 0xaaaaaaaaaaaaab); - assertEq(exponent, -57); - - (mantissa, exponent) = UFloatMath.fromRational(123456789123456789, 1000000000).decode(); - assertEq(mantissa, 0xeb79a2a3f35ba7); - assertEq(exponent, -29); - } - - /** - * @dev Test encoding `mantissa` and `exponent` into `UFloat9x56` - */ - function test_encode() external { - // Encode zero - assertEq(UFloatMath.encode(0, 0), UFloatMath.ZERO); - assertEq(UFloatMath.encode(0, -311), UFloatMath.ZERO); - assertEq(UFloatMath.encode(0, -310), UFloatMath.ZERO); - assertEq(UFloatMath.encode(0, 200), UFloatMath.ZERO); - - // Normal numbers - assertEq(UFloatMath.encode(0x80000000000000, -55), UFloatMath.ONE); - assertEq(UFloatMath.encode(UFloatMath.MANTISSA_MAX, 200), UFloatMath.MAX); - assertEq(UFloatMath.encode(0xaaaaaaaaaaaaab, -57), UFloatMath.fromRational(1, 3)); - assertEq(UFloatMath.encode(0xeb79a2a3f35ba7, -29), UFloatMath.fromRational(123456789123456789, 1000000000)); - assertEq(UFloatMath.encode(UFloatMath.MANTISSA_MIN, -310), UFloat9x56.wrap(0x0080000000000000)); - - // Subnormal numbers - assertEq(UFloatMath.encode(0x00000000000000, -311), UFloat9x56.wrap(0x0000000000000000)); - assertEq(UFloatMath.encode(0x00000000000001, -310), UFloat9x56.wrap(0x0000000000000001)); - assertEq(UFloatMath.encode(0x7fffffffffffff, -310), UFloat9x56.wrap(0x007fffffffffffff)); - - // Revert if the exponent is out of bounds - vm.expectRevert("UFloat9x56: exponent out of bounds"); - mock.encode(0, -312); - vm.expectRevert("UFloat9x56: exponent out of bounds"); - mock.encode(0, 201); - - // Revert if the mantissa is invalid - vm.expectRevert("UFloat9x56: invalid mantissa"); - mock.encode(1, 0); - vm.expectRevert("UFloat9x56: invalid mantissa"); - mock.encode(UFloatMath.MANTISSA_MAX + 1, 200); - vm.expectRevert("UFloat9x56: invalid mantissa"); - mock.encode(UFloatMath.MANTISSA_MIN - 1, -309); - } - - /** - * @dev Fuzz test converting between `uint256` and `UFloat9x56`. - * The conversion must be exact given x is 56 bit long. - */ - function test_fuzzConvertUint(uint56 mantissa, uint8 exponent) external pure { - unchecked { - uint256 value = uint256(mantissa) << uint256(exponent); - UFloat9x56 float = UFloatMath.fromUint(value); - assertEq(float.truncate(), value); - } - } - - /** - * @dev Fuzz test `UFloatMath.mul` and `UFloatMath.saturatingMul`. - */ - function test_fuzzMultiplication(uint56 mantissa, uint8 exponent, uint256 multiplier) external pure { - unchecked { - uint256 value = uint256(mantissa) << uint256(exponent); - UFloat9x56 float = UFloatMath.fromUint(value); - assertEq(float.mul(multiplier), value * multiplier); - assertEq(float.saturatingMul(multiplier), value.saturatingMul(multiplier)); - } - } - - /** - * @dev Compare two `UFloat`. - */ - function assertEq(UFloat9x56 left, UFloat9x56 right) internal pure { - assertEq(UFloat9x56.unwrap(left), UFloat9x56.unwrap(right)); - } - - function assertEq(UFloat9x56 left, UFloat9x56 right, string memory message) internal pure { - assertEq(UFloat9x56.unwrap(left), UFloat9x56.unwrap(right), message); - } - - /** - * @dev Computes `num * 2**exp / den` with the given rounding direction. - */ - function muldiv(uint256 num, int256 exp, uint256 den, Rounding rounding) internal pure returns (uint256) { - uint256 numzeros = num.leadingZeros(); - uint256 denzeros = den.leadingZeros(); - assertLe(exp, int256(denzeros)); - assertGe(exp, -int256(numzeros + 255)); - - if (exp < -255) { - num <<= uint256(-exp) - 255; - exp = 255; - } else if (exp <= 0) { - exp = -exp; - } else { - den <<= uint256(exp); - exp = 0; - } - - return BranchlessMath.mulDiv(num, 1 << uint256(exp), den, rounding); - } - - /** - * @dev When `numerator` and `denominator` are exact, the conversion must be exact. - */ - function checkfromRationalRouding(uint256 num, uint256 den, UFloat9x56 expected) internal pure { - (uint256 mantissa, int256 exponent) = expected.decode(); - assertGt(mantissa + BranchlessMath.toUint(expected.eq(UFloatMath.ZERO)), 0, "mantissa is zero"); - uint256 value = muldiv(num, exponent, den, Rounding.Floor); - assertEq(mantissa, value, "Rounding.Floor failed"); - - bool roundUp = muldiv(num, exponent, den, Rounding.Nearest) > value && expected.lt(UFloatMath.MAX); - expected = UFloat9x56.wrap(uint64(UFloat9x56.unwrap(expected) + BranchlessMath.toUint(roundUp))); - (mantissa, exponent) = expected.decode(); - value = muldiv(num, exponent, den, Rounding.Nearest); - assertEq(mantissa, value, "Rounding.Nearest failed"); - - roundUp = muldiv(num, exponent, den, Rounding.Ceil) > value && expected.lt(UFloatMath.MAX); - expected = UFloat9x56.wrap(uint64(UFloat9x56.unwrap(expected) + BranchlessMath.toUint(roundUp))); - (mantissa, exponent) = expected.decode(); - value = muldiv(num, exponent, den, Rounding.Ceil); - assertEq(mantissa, value, "Rounding.Ceil failed"); - } - - /** - * @dev Converts numerator / denominator to `UFloat9x56`, following the selected rounding direction. - */ - function test_fromRational() external pure { - // Normal numbers - checkfromRationalRouding(0, 1, UFloatMath.ZERO); - checkfromRationalRouding(0, 2, UFloatMath.ZERO); - checkfromRationalRouding(0, type(uint256).max, UFloatMath.ZERO); - checkfromRationalRouding(1, 1, UFloatMath.ONE); - checkfromRationalRouding(2, 2, UFloatMath.ONE); - checkfromRationalRouding(UFloatMath.MANTISSA_MAX, UFloatMath.MANTISSA_MAX, UFloatMath.ONE); - checkfromRationalRouding(type(uint256).max, type(uint256).max, UFloatMath.ONE); - checkfromRationalRouding(0x2ffffffffffffff, 3, UFloat9x56.wrap(0x9bffffffffffffff)); - - // Test rounding - assertEq(UFloatMath.fromRational(0x2ffffffffffffff, 3, Rounding.Floor), UFloat9x56.wrap(0x9bffffffffffffff)); - assertEq(UFloatMath.fromRational(0x2ffffffffffffff, 3, Rounding.Nearest), UFloat9x56.wrap(0x9c00000000000000)); - assertEq(UFloatMath.fromRational(0x2ffffffffffffff, 3, Rounding.Ceil), UFloat9x56.wrap(0x9c00000000000000)); - } - - /** - * @dev Converts numerator / denominator to `UFloat9x56`, following the selected rounding direction. - */ - function test_fuzzFromRational(uint248 numerator, uint248 denominator) external pure { - vm.assume(denominator > 0 && numerator > 0); - uint256 numbits = BranchlessMath.log2(numerator); - uint256 denbits = BranchlessMath.log2(denominator); - vm.assume(numbits.absDiff(denbits) <= 200); - unchecked { - UFloat9x56 float = UFloatMath.fromRational(numerator, denominator, Rounding.Floor); - checkfromRationalRouding(numerator, denominator, float); - uint256 integer = uint256(numerator) / uint256(denominator); - assertTrue(float.eq(integer), "float is not equal to integer"); - - // Find a multiplier such that (multipler / numerator) * numerator > 0 - uint256 multiplier = denbits.saturatingSub(numbits) + UFloatMath.MANTISSA_DIGITS - 1; - multiplier = multiplier.min(255 - numbits); - multiplier = 2 ** multiplier; - - // Calculate numerator * multipler / numerator - integer = BranchlessMath.mulDiv(numerator, multiplier, denominator, Rounding.Floor); - { - // Keep only the `MANTISSA_DIGITS` most significant bits. - uint256 shift = integer.log2().saturatingSub(UFloatMath.MANTISSA_DIGITS - 1); - uint256 mask = type(uint256).max << shift; - integer &= mask; - } - assertGt(integer, 0, "integer is zero"); - assertEq(float.mul(multiplier), integer); - } - } - - /** - * @dev Convert `UFloat9x56` to a rational number, returns the numerator and denominator, respectively. - * Obs: Values above 2**-256 are represented precisely, values below are approximated or round down to zero. - */ - function test_toRational(uint56 mantissa, uint16 exponent) external pure { - // Make sure the exponent is within 9 bit bounds. - exponent %= 2 ** 9; - - // Doesn't allow exponent == 0, once subnormal numbers cannot be converted to rational precisely. - vm.assume(exponent > 0); - unchecked { - UFloat9x56 float = - UFloat9x56.wrap(uint64(mantissa) | uint64(exponent) << uint64(UFloatMath.MANTISSA_DIGITS - 1)); - (uint256 numerator, uint256 denominator) = float.toRational(); - uint256 numbits = numerator.log2(); - uint256 denbits = denominator.log2(); - - uint256 integer = uint256(numerator) / uint256(denominator); - assertTrue(float.eq(integer), "float is not equal to integer"); - - // Find a multiplier such that (multipler / numerator) * numerator > 0 - uint256 multiplier = denbits.saturatingSub(numbits) + UFloatMath.MANTISSA_DIGITS - 1; - multiplier = multiplier.min(255 - numbits); - multiplier = 2 ** multiplier; - - // Calculate numerator * multipler / numerator - integer = BranchlessMath.mulDiv(numerator, multiplier, denominator, Rounding.Floor); - { - // Keep only the `MANTISSA_DIGITS` most significant bits. - uint256 shift = integer.log2().saturatingSub(UFloatMath.MANTISSA_DIGITS - 1); - uint256 mask = type(uint256).max << shift; - integer &= mask; - } - assertGt(integer, 0, "integer is zero"); - assertEq(float.mul(multiplier), integer); - } - } -} diff --git a/analog-gmp/test/GasUtils.t.sol b/analog-gmp/test/GasUtils.t.sol deleted file mode 100644 index 86cd2df14..000000000 --- a/analog-gmp/test/GasUtils.t.sol +++ /dev/null @@ -1,241 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (test/GasUtils.t.sol) - -pragma solidity >=0.8.0; - -import {Signer} from "frost-evm/sol/Signer.sol"; -import {Test, console} from "forge-std/Test.sol"; -import {VmSafe} from "forge-std/Vm.sol"; -import {TestUtils} from "./TestUtils.sol"; -import {BaseTest} from "./utils/BaseTest.sol"; -import {GasSpender} from "./utils/GasSpender.sol"; -import {Gateway, GatewayEIP712} from "../src/Gateway.sol"; -import {GatewayProxy} from "../src/GatewayProxy.sol"; -import {GasUtils} from "../src/utils/GasUtils.sol"; -import {BranchlessMath} from "../src/utils/BranchlessMath.sol"; -import {UFloat9x56, UFloatMath} from "../src/utils/Float9x56.sol"; -import {IGateway} from "../src/interfaces/IGateway.sol"; -import {IGmpReceiver} from "../src/interfaces/IGmpReceiver.sol"; -import {IExecutor} from "../src/interfaces/IExecutor.sol"; -import {CallOptions, GatewayUtils} from "./Gateway.t.sol"; -import { - GmpMessage, - UpdateKeysMessage, - Signature, - TssKey, - Network, - GmpStatus, - PrimitiveUtils, - GmpSender -} from "../src/Primitives.sol"; - -uint256 constant secret = 0x42; -uint256 constant nonce = 0x69; - -contract GasUtilsMock { - function execute(Signature calldata, GmpMessage calldata) - external - pure - returns (uint256 baseCost, uint256 nonZeros, uint256 zeros) - { - baseCost = GasUtils.txBaseCost(); - nonZeros = GasUtils.countNonZerosCalldata(msg.data); - zeros = msg.data.length - nonZeros; - } -} - -contract GasUtilsTest is BaseTest { - using PrimitiveUtils for UpdateKeysMessage; - using PrimitiveUtils for GmpMessage; - using PrimitiveUtils for GmpSender; - using PrimitiveUtils for address; - using GatewayUtils for CallOptions; - using BranchlessMath for uint256; - - GasUtilsMock internal mock; - Gateway internal gateway; - Signer internal signer; - - // Receiver Contract, the will waste the exact amount of gas you sent to it in the data field - IGmpReceiver internal receiver; - - uint16 private constant SRC_NETWORK_ID = 1234; - uint16 internal constant DEST_NETWORK_ID = 1337; - - constructor() { - TestUtils.deployFactory(); - - // Create the Shard and Admin accounts - signer = new Signer(secret); - VmSafe.Wallet memory deployer = vm.createWallet(secret); - vm.deal(deployer.addr, 100 ether); - - // Deploy the GasUtilsMock contract - mock = new GasUtilsMock(); - - // Deploy the GatewayProxy - gateway = Gateway( - payable(address(TestUtils.setupGateway(deployer, bytes32(uint256(0)), SRC_NETWORK_ID, DEST_NETWORK_ID))) - ); - vm.deal(address(gateway), 100 ether); - - // Deploy the GasSpender contract, which implements the IGmpReceiver interface. - receiver = IGmpReceiver(new GasSpender()); - } - - function sign(GmpMessage memory gmp) internal view returns (Signature memory) { - uint256 hash = uint256(gmp.eip712hash()); - (uint256 e, uint256 s) = signer.signPrehashed(hash, nonce); - return Signature({xCoord: signer.xCoord(), e: e, s: s}); - } - - /** - * @dev Create a GMP message with the provided parameters. - */ - function _buildGmpMessage(address sender, uint64 gasLimit, uint64 gasUsed, uint256 messageSize) - private - view - returns (GmpMessage memory message, Signature memory signature, CallOptions memory context) - { - require(gasUsed == 0 || messageSize >= 32, "If gasUsed > 0, then messageSize must be >= 32"); - require(messageSize <= 0x6000, "message is too big"); - - // Setup data and receiver addresses. - bytes memory data = new bytes(messageSize); - address gmpReceiver; - if (gasUsed > 0) { - gmpReceiver = address(receiver); - assembly { - mstore(add(data, 32), gasUsed) - } - } else { - // Create a new unique receiver address for each message, otherwise the gas refund will not work. - gmpReceiver = address(bytes20(keccak256(abi.encode(sender, gasLimit, messageSize)))); - } - - // Build the GMP message - message = GmpMessage({ - source: sender.toSender(false), - srcNetwork: SRC_NETWORK_ID, - dest: gmpReceiver, - destNetwork: DEST_NETWORK_ID, - gasLimit: gasLimit, - nonce: 0, - data: data - }); - - // Sign the message - signature = sign(message); - - // Calculate memory expansion cost and base cost - (uint256 baseCost, uint256 executionCost) = GatewayUtils.computeGmpGasCost(signature, message); - - // Set Transaction Parameters - context = CallOptions({ - from: sender, - to: address(gateway), - value: 0, - gasLimit: GasUtils.executionGasNeeded(message.data.length, message.gasLimit).saturatingAdd(baseCost), - executionCost: executionCost, - baseCost: baseCost - }); - } - - /** - * Test the `GasUtils.txBaseCost` method. - */ - function test_txBaseCost() external view { - // Build and sign GMP message - GmpMessage memory gmp = GmpMessage({ - source: address(0x1111111111111111111111111111111111111111).toSender(false), - srcNetwork: 1234, - dest: address(0x2222222222222222222222222222222222222222), - destNetwork: 1337, - gasLimit: 0, - nonce: 0, - data: hex"00" - }); - Signature memory sig = sign(gmp); - sig.xCoord = type(uint256).max; - sig.e = type(uint256).max; - sig.s = type(uint256).max; - - // Check if `IExecutor.execute` match the expected base cost - (uint256 baseCost, uint256 nonZeros, uint256 zeros) = mock.execute(sig, gmp); - assertEq(baseCost, 24444, "Wrong calldata gas cost"); - assertEq(nonZeros, 147, "wrong number of non-zeros"); - assertEq(zeros, 273, "wrong number of zeros"); - } - - /** - * @dev Compare the estimated gas cost VS the actual gas cost of the `execute` method. - */ - function test_baseExecutionCost(uint16 messageSize, uint16 gasLimit) external { - vm.assume(gasLimit >= 5000); - vm.assume(messageSize <= (0x6000 - 32)); - messageSize += 32; - vm.txGasPrice(1); - address sender = TestUtils.createTestAccount(100 ether); - - // Build the GMP message - GmpMessage memory gmp; - Signature memory sig; - CallOptions memory ctx; - (gmp, sig, ctx) = _buildGmpMessage(sender, gasLimit, gasLimit, messageSize); - - // Increase the gas limit to avoid out-of-gas errors - ctx.gasLimit = ctx.gasLimit.saturatingAdd(10_000_000); - - // Execute the GMP message - { - bytes32 gmpId = gmp.eip712hash(); - vm.expectEmit(true, true, true, true); - emit IExecutor.GmpExecuted(gmpId, gmp.source, gmp.dest, GmpStatus.SUCCESS, bytes32(uint256(gasLimit))); - uint256 balanceBefore = ctx.from.balance; - (GmpStatus status, bytes32 result) = ctx.execute(sig, gmp); - assertEq(uint256(status), uint256(GmpStatus.SUCCESS), "GMP execution failed"); - assertEq(result, bytes32(uint256(gasLimit)), "unexpected result"); - assertEq(balanceBefore, ctx.from.balance, "Balance should not change"); - } - - emit log_named_uint("execution cost", GasUtils._executionGasCost(gmp.data.length, gmp.gasLimit)); - uint256 executionCost = GasUtils.computeExecutionRefund(uint16(gmp.data.length), gmp.gasLimit); - assertEq(ctx.executionCost, executionCost, "execution cost mismatch"); - - // Calculate the expected base cost - uint256 dynamicCost = executionCost - GasUtils.EXECUTION_BASE_COST; - uint256 expectedBaseCost = ctx.executionCost - dynamicCost; - { - console.log("proxy: ", ctx.to); - console.logBytes(ctx.to.code); - address implementationAddr = address( - uint160(uint256(vm.load(ctx.to, 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc))) - ); - console.log("implementation: ", implementationAddr); - console.logBytes(implementationAddr.code); - console.log("calldata:"); - console.logBytes(abi.encodeCall(IExecutor.execute, (sig, gmp))); - } - assertEq(expectedBaseCost, GasUtils.EXECUTION_BASE_COST, "Wrong EXECUTION_BASE_COST"); - } - - function test_gasUtils() external pure { - uint256 baseCost = GasUtils.EXECUTION_BASE_COST; - assertEq(GasUtils.estimateGas(0, 0, 0), 31528 + baseCost); - assertEq(GasUtils.estimateGas(0, 33, 0), 31901 + baseCost); - assertEq(GasUtils.estimateGas(33, 0, 0), 32561 + baseCost); - assertEq(GasUtils.estimateGas(20, 13, 0), 32301 + baseCost); - - UFloat9x56 one = UFloatMath.ONE; - assertEq(GasUtils.estimateWeiCost(one, 0, 0, 0, 0), 31528 + baseCost); - assertEq(GasUtils.estimateWeiCost(one, 0, 0, 33, 0), 31901 + baseCost); - assertEq(GasUtils.estimateWeiCost(one, 0, 33, 0, 0), 32561 + baseCost); - assertEq(GasUtils.estimateWeiCost(one, 0, 20, 13, 0), 32301 + baseCost); - - UFloat9x56 two = UFloat9x56.wrap(0x8080000000000000); - assertEq(GasUtils.estimateWeiCost(two, 0, 0, 0, 0), (31528 + baseCost) * 2); - assertEq(GasUtils.estimateWeiCost(two, 0, 0, 33, 0), (31901 + baseCost) * 2); - assertEq(GasUtils.estimateWeiCost(two, 0, 33, 0, 0), (32561 + baseCost) * 2); - assertEq(GasUtils.estimateWeiCost(two, 0, 20, 13, 0), (32301 + baseCost) * 2); - } -} diff --git a/analog-gmp/test/Gateway.t.sol b/analog-gmp/test/Gateway.t.sol deleted file mode 100644 index 82c0933b9..000000000 --- a/analog-gmp/test/Gateway.t.sol +++ /dev/null @@ -1,788 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (test/Gateway.t.sol) - -pragma solidity >=0.8.0; - -import {Test, console, Vm} from "forge-std/Test.sol"; -import {VmSafe} from "forge-std/Vm.sol"; -import {TestUtils, SigningKey, SigningUtils} from "./TestUtils.sol"; -import {GasSpender} from "./utils/GasSpender.sol"; -import {BaseTest} from "./utils/BaseTest.sol"; -import {Gateway, GatewayEIP712} from "../src/Gateway.sol"; -import {GatewayProxy} from "../src/GatewayProxy.sol"; -import {GasUtils} from "../src/utils/GasUtils.sol"; -import {BranchlessMath} from "../src/utils/BranchlessMath.sol"; -import {UFloat9x56, UFloatMath} from "../src/utils/Float9x56.sol"; -import {IGateway} from "../src/interfaces/IGateway.sol"; -import {IGmpReceiver} from "../src/interfaces/IGmpReceiver.sol"; -import {IExecutor} from "../src/interfaces/IExecutor.sol"; -import { - GmpMessage, - UpdateKeysMessage, - Signature, - TssKey, - Network, - GmpStatus, - PrimitiveUtils, - GmpSender, - GMP_VERSION -} from "../src/Primitives.sol"; - -contract SigUtilsTest is GatewayEIP712, Test { - using PrimitiveUtils for GmpMessage; - - constructor() GatewayEIP712(69, address(0)) {} - - function testPayload() public pure { - GmpMessage memory gmp = GmpMessage({ - source: GmpSender.wrap(0x0), - srcNetwork: 42, - dest: address(0x0), - destNetwork: 69, - gasLimit: 0, - nonce: 0, - data: "" - }); - bytes32 typedHash = gmp.eip712hash(); - bytes32 expected = keccak256( - abi.encode( - GMP_VERSION, - gmp.source, - gmp.srcNetwork, - gmp.dest, - gmp.destNetwork, - gmp.gasLimit, - gmp.nonce, - keccak256(gmp.data) - ) - ); - assertEq(typedHash, expected); - } -} - -struct CallOptions { - address from; - address to; - uint256 value; - uint256 gasLimit; - uint256 executionCost; - uint256 baseCost; -} - -library GatewayUtils { - function tryExecute(CallOptions memory ctx, Signature memory signature, GmpMessage memory message) - internal - returns (bool success, GmpStatus status, bytes32 result) - { - bytes memory encodedCall = abi.encodeCall(IExecutor.execute, (signature, message)); - bytes memory output; - (ctx.executionCost, ctx.baseCost, success, output) = - TestUtils.tryExecuteCall(ctx.from, ctx.to, ctx.gasLimit, ctx.value, encodedCall); - - if (success) { - require(output.length == 64, "unexpected output length for IExecutor.execute method"); - assembly { - let ptr := add(output, 32) - status := mload(ptr) - result := mload(add(ptr, 32)) - } - } else { - status = GmpStatus.NOT_FOUND; - result = bytes32(0); - } - } - - function execute(CallOptions memory ctx, Signature memory signature, GmpMessage memory message) - internal - returns (GmpStatus status, bytes32 result) - { - bytes memory encodedCall = abi.encodeCall(IExecutor.execute, (signature, message)); - (uint256 executionCost, uint256 baseCost, bytes memory output) = - TestUtils.executeCall(ctx.from, ctx.to, ctx.gasLimit, ctx.value, encodedCall); - - ctx.executionCost = executionCost; - ctx.baseCost = baseCost; - if (output.length == 64) { - assembly { - let ptr := add(output, 32) - status := mload(ptr) - result := mload(add(ptr, 32)) - } - } - } - - function submitMessage(CallOptions memory ctx, GmpMessage memory gmp) internal returns (bytes32 result) { - bytes memory encodedCall = - abi.encodeCall(IGateway.submitMessage, (gmp.dest, gmp.destNetwork, gmp.gasLimit, gmp.data)); - (uint256 executionCost, uint256 baseCost, bytes memory output) = - TestUtils.executeCall(ctx.from, ctx.to, ctx.gasLimit, ctx.value, encodedCall); - ctx.executionCost = executionCost; - ctx.baseCost = baseCost; - if (output.length == 32) { - assembly { - result := mload(add(output, 32)) - } - } - } - - function computeGmpGasCost(Signature memory signature, GmpMessage memory message) - internal - pure - returns (uint256 baseCost, uint256 executionCost) - { - executionCost = GasUtils.computeExecutionRefund(uint16(message.data.length), 0); - bytes memory encodedCall = abi.encodeCall(IExecutor.execute, (signature, message)); - baseCost = TestUtils.calculateBaseCost(encodedCall); - } -} - -// contract GatewayBase is Test { -contract GatewayTest is BaseTest { - using PrimitiveUtils for UpdateKeysMessage; - using PrimitiveUtils for GmpMessage; - using PrimitiveUtils for GmpSender; - using PrimitiveUtils for address; - using GatewayUtils for CallOptions; - using BranchlessMath for uint256; - using SigningUtils for SigningKey; - - Gateway internal gateway; - - // Chronicle TSS Secret - uint256 private constant SECRET = 0x42; - uint256 private constant SIGNING_NONCE = 0x69; - - // Receiver Contract, the will waste the exact amount of gas you sent to it in the data field - IGmpReceiver internal receiver; - - // Netowrk ids - uint16 private constant SRC_NETWORK_ID = 1234; - uint16 internal constant DEST_NETWORK_ID = 1337; - - address internal constant ADMIN = 0x6f4c950442e1Af093BcfF730381E63Ae9171b87a; - - constructor() { - VmSafe.Wallet memory admin = vm.createWallet(SECRET); - assertEq(ADMIN, admin.addr, "admin address mismatch"); - gateway = Gateway( - payable(address(TestUtils.setupGateway(admin, bytes32(uint256(1234)), SRC_NETWORK_ID, DEST_NETWORK_ID))) - ); - receiver = IGmpReceiver(new GasSpender()); - } - - function setUp() external view { - // check block gas limit as gas left - assertEq(block.gaslimit, 30_000_000); - assertTrue(gasleft() >= 10_000_000); - } - - function sign(GmpMessage memory gmp) internal pure returns (Signature memory) { - bytes32 hash = gmp.eip712hash(); - SigningKey memory signer = TestUtils.createSigner(SECRET); - (uint256 e, uint256 s) = signer.signPrehashed(hash, SIGNING_NONCE); - return Signature({xCoord: signer.xCoord(), e: e, s: s}); - } - - function _sortTssKeys(TssKey[] memory keys) private pure { - // sort keys by xCoord - for (uint256 i = 0; i < keys.length; i++) { - for (uint256 j = i + 1; j < keys.length; j++) { - if (keys[i].xCoord > keys[j].xCoord) { - TssKey memory temp = keys[i]; - keys[i] = keys[j]; - keys[j] = temp; - } - } - } - } - - function test_withinSizeLimit() external { - bytes memory implementationCreationCode = - abi.encodePacked(type(Gateway).creationCode, abi.encode(DEST_NETWORK_ID, address(gateway))); - address implementation = - FACTORY.create2(bytes32(uint256(1337)), implementationCreationCode, abi.encode(DEST_NETWORK_ID)); - assertLt(implementation.code.length, 0x6000, "implementation code length is too large"); - } - - function test_setShards() external { - TssKey[] memory keys = new TssKey[](10); - - // create random shard keys - SigningKey memory signer; - for (uint256 i = 0; i < keys.length; i++) { - signer = TestUtils.signerFromEntropy(bytes32(i)); - keys[i] = TssKey({yParity: signer.yParity() == 28 ? 3 : 2, xCoord: signer.xCoord()}); - } - _sortTssKeys(keys); - - // Only admin can set shards keys - vm.expectRevert("unauthorized"); - gateway.setShards(keys); - - // Set shards keys must work - vm.prank(ADMIN, ADMIN); - gateway.setShards(keys); - - // Check shards keys - TssKey[] memory shards = gateway.shards(); - _sortTssKeys(shards); - for (uint256 i = 0; i < shards.length; i++) { - assertEq(shards[i].xCoord, keys[i].xCoord); - assertEq(shards[i].yParity, keys[i].yParity); - } - - // // Replace one shard key - signer = TestUtils.signerFromEntropy(bytes32(uint256(12345))); - keys[0].xCoord = signer.xCoord(); - keys[0].yParity = signer.yParity() == 28 ? 3 : 2; - _sortTssKeys(keys); - vm.prank(ADMIN, ADMIN); - gateway.setShards(keys); - - // Check shards keys - shards = gateway.shards(); - _sortTssKeys(shards); - for (uint256 i = 0; i < shards.length; i++) { - assertEq(shards[i].xCoord, keys[i].xCoord); - assertEq(shards[i].yParity, keys[i].yParity); - } - } - - function test_shardEvents() external { - TssKey[] memory keys = new TssKey[](10); - - // create random shard keys - SigningKey memory signer; - for (uint256 i = 0; i < keys.length; i++) { - signer = TestUtils.signerFromEntropy(bytes32(i)); - keys[i] = TssKey({yParity: signer.yParity() == 28 ? 3 : 2, xCoord: signer.xCoord()}); - } - _sortTssKeys(keys); - - // set shards - vm.prank(ADMIN, ADMIN); - vm.expectEmit(false, false, false, true); - emit IExecutor.ShardsRegistered(keys); - gateway.setShards(keys); - - // set a shard which is already registered and verify that is does not emit a event. - vm.prank(ADMIN, ADMIN); - vm.recordLogs(); - gateway.setShard(keys[0]); - Vm.Log[] memory entries = vm.getRecordedLogs(); - assertEq(entries.length, 0); - - // Revoke a registered shard thats not registered. - uint256 unregisteredSignerKey = 11; - signer = TestUtils.signerFromEntropy(bytes32(unregisteredSignerKey)); - TssKey memory nonRegisteredKey = TssKey({yParity: signer.yParity() == 28 ? 3 : 2, xCoord: signer.xCoord()}); - vm.prank(ADMIN, ADMIN); - vm.recordLogs(); - gateway.revokeShard(nonRegisteredKey); - Vm.Log[] memory entries1 = vm.getRecordedLogs(); - assertEq(entries1.length, 0); - - // Revoke a registered shard - vm.prank(ADMIN, ADMIN); - TssKey[] memory unregisteredShardKey = new TssKey[](1); - unregisteredShardKey[0] = keys[0]; - vm.expectEmit(false, false, false, true); - emit IExecutor.ShardsUnregistered(unregisteredShardKey); - gateway.revokeShard(keys[0]); - - // Register a revoked shard - vm.prank(ADMIN, ADMIN); - vm.expectEmit(false, false, false, true); - emit IExecutor.ShardsRegistered(unregisteredShardKey); - gateway.setShard(unregisteredShardKey[0]); - - // Revoke half of the keys and verify event length - vm.prank(ADMIN, ADMIN); - uint256 halfKeysLength = keys.length / 2; - uint256 secondHalfLength = keys.length - halfKeysLength; - TssKey[] memory firstHalf = new TssKey[](halfKeysLength); - TssKey[] memory secondHalf = new TssKey[](secondHalfLength); - for (uint256 i = 0; i < keys.length; i++) { - if (i < halfKeysLength) { - firstHalf[i] = keys[i]; - } else { - secondHalf[i - halfKeysLength] = keys[i]; - } - } - vm.expectEmit(false, false, false, true); - emit IExecutor.ShardsUnregistered(firstHalf); - gateway.revokeShards(firstHalf); - - // register first half keys and check if the other half is unregistered - vm.prank(ADMIN, ADMIN); - vm.expectEmit(false, false, false, true); - emit IExecutor.ShardsRegistered(firstHalf); - emit IExecutor.ShardsUnregistered(secondHalf); - gateway.setShards(firstHalf); - } - - function test_Receiver() external { - bytes memory testEncodedCall = abi.encodeCall( - IGmpReceiver.onGmpReceived, - ( - 0x0000000000000000000000000000000000000000000000000000000000000000, - 1, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0, - abi.encode(uint256(1234)) - ) - ); - // Calling the receiver contract directly to make the address warm - address sender = TestUtils.createTestAccount(10 ether); - (uint256 gasUsed,, bytes memory output) = - TestUtils.executeCall(sender, address(receiver), 23_318, 0, testEncodedCall); - assertEq(gasUsed, 1234); - assertEq(output.length, 32); - } - - function test_estimateMessageCost() external { - vm.txGasPrice(1); - uint256 cost = gateway.estimateMessageCost(DEST_NETWORK_ID, 96, 100000); - assertEq(cost, GasUtils.EXECUTION_BASE_COST + 133821); - } - - function test_checkPayloadSize() external { - vm.txGasPrice(1); - address sender = TestUtils.createTestAccount(100 ether); - - // Build and sign GMP message - GmpMessage memory gmp = GmpMessage({ - source: sender.toSender(false), - srcNetwork: SRC_NETWORK_ID, - dest: address(bytes20(keccak256("dummy_address"))), - destNetwork: DEST_NETWORK_ID, - gasLimit: 0, - nonce: 0, - data: new bytes(24576 + 1) - }); - - Signature memory sig = sign(gmp); - - // Calculate memory expansion cost and base cost - (uint256 baseCost, uint256 executionCost) = GatewayUtils.computeGmpGasCost(sig, gmp); - - // Transaction Parameters - CallOptions memory ctx = CallOptions({ - from: sender, - to: address(gateway), - value: 0, - gasLimit: GasUtils.executionGasNeeded(gmp.data.length, gmp.gasLimit) + baseCost + 1_000_000, - executionCost: 0, - baseCost: 0 - }); - - GmpStatus status; - bytes32 returned; - - // Expect a revert - vm.expectRevert("msg data too large"); - (status, returned) = ctx.execute(sig, gmp); - assertLt(ctx.executionCost, executionCost, "revert should use less gas!!"); - assertEq(ctx.baseCost, baseCost, "unexpected base cost"); - } - - /** - * @dev Test the gas metering for the `execute` function. - */ - function test_gasMeter(uint16 messageSize) external { - vm.assume(messageSize <= 0x6000 && messageSize >= 32); - vm.txGasPrice(1); - address sender = TestUtils.createTestAccount(100 ether); - - // Build and sign GMP message - GmpMessage memory gmp = GmpMessage({ - source: sender.toSender(false), - srcNetwork: SRC_NETWORK_ID, - dest: address(receiver), - destNetwork: DEST_NETWORK_ID, - gasLimit: 1000, - nonce: 0, - data: new bytes(messageSize) - }); - { - bytes memory gmpData = gmp.data; - assembly { - mstore(add(gmpData, 0x20), 1000) - } - } - Signature memory sig = sign(gmp); - - // Calculate memory expansion cost and base cost - (uint256 baseCost, uint256 executionCost) = GatewayUtils.computeGmpGasCost(sig, gmp); - executionCost += gmp.gasLimit; - - // Transaction Parameters - CallOptions memory ctx = CallOptions({ - from: sender, - to: address(gateway), - value: 0, - gasLimit: GasUtils.executionGasNeeded(gmp.data.length, gmp.gasLimit) + baseCost - 1, - executionCost: 0, - baseCost: 0 - }); - - GmpStatus status; - bytes32 returned; - - // Expect a revert - vm.expectRevert(); - (status, returned) = ctx.execute(sig, gmp); - - // Check if the gateway has enough balance to refund the gas - uint256 gatewayBalance = address(gateway).balance; - uint256 senderBalance = address(sender).balance; - assertGe(gatewayBalance, executionCost + baseCost); - assertGe(senderBalance, ctx.gasLimit + ctx.value); - - // Give sufficient gas - ctx.gasLimit += 1; - ctx.executionCost = 0; - ctx.baseCost = 0; - (status, returned) = ctx.execute(sig, gmp); - - assertEq(uint256(status), uint256(GmpStatus.SUCCESS), "gmp execution failed"); - assertEq(uint256(returned), gmp.gasLimit, "wrong gmp return value"); - assertEq(ctx.baseCost, baseCost, "ctx.baseCost != baseCost"); - assertEq(ctx.executionCost, executionCost, "ctx.executionCost != executionCost"); - assertEq(gatewayBalance - address(gateway).balance, executionCost + baseCost, "wrong refund amount"); - assertEq(senderBalance, address(sender).balance, "sender balance should not change"); - assertEq( - ctx.gasLimit - baseCost, GasUtils.executionGasNeeded(gmp.data.length, gmp.gasLimit), "gas needed mismatch" - ); - - // Submit GMP message - { - // Calculate the minimal gmp value minus one - uint256 nonZeros = GasUtils.countNonZeros(gmp.data); - uint256 zeros = gmp.data.length - nonZeros; - ctx.value = GasUtils.estimateGas(uint16(nonZeros), uint16(zeros), gmp.gasLimit) - 1; - - // Add sufficient gas - ctx.gasLimit += gmp.data.length * 8; - } - - // Must revert if fund are insufficient - vm.expectRevert("insufficient tx value"); - ctx.submitMessage(gmp); - - { - bytes memory submitEncoded = - abi.encodeCall(IGateway.submitMessage, (gmp.dest, gmp.destNetwork, gmp.gasLimit, gmp.data)); - assertEq(submitEncoded.length, ((gmp.data.length + 31) & 0xffe0) + 164, "wrong encoded length"); - } - - // Must work if the funds are sufficient - ctx.value += 1; - ctx.submitMessage(gmp); - - assertEq( - ctx.executionCost, - GasUtils.submitMessageGasCost(uint16(gmp.data.length)) - 4500 + 17100, - "unexpected submit message gas cost" - ); - } - - function test_submitMessageMeter(uint16 messageSize) external { - vm.assume(messageSize <= 0x6000); - vm.txGasPrice(1); - address sender = TestUtils.createTestAccount(1000 ether); - - // Build and sign GMP message - GmpMessage memory gmp = GmpMessage({ - source: sender.toSender(false), - srcNetwork: DEST_NETWORK_ID, - dest: address(bytes20(keccak256("dummy_address"))), - destNetwork: DEST_NETWORK_ID, - gasLimit: 0, - nonce: 0, - data: new bytes(messageSize) - }); - - // Calculate memory expansion cost and base cost - uint256 baseCost; - { - bytes memory encoded = - abi.encodeCall(IGateway.submitMessage, (gmp.dest, gmp.destNetwork, gmp.gasLimit, gmp.data)); - assertEq(encoded.length, ((gmp.data.length + 31) & 0xffe0) + 164, "wrong encoded length"); - baseCost = TestUtils.calculateBaseCost(encoded); - } - - // Transaction Parameters - CallOptions memory ctx = CallOptions({ - from: sender, - to: address(gateway), - value: 0, - gasLimit: GasUtils.submitMessageGasNeeded(uint16(gmp.data.length)) + baseCost, - executionCost: 0, - baseCost: 0 - }); - - // Submit the transaction - { - uint256 nonZeros = GasUtils.countNonZeros(gmp.data); - uint256 zeros = gmp.data.length - nonZeros; - ctx.value = GasUtils.estimateGas(uint16(nonZeros), uint16(zeros), gmp.gasLimit); - } - - uint256 snapshot = vm.snapshotState(); - // Must work if the funds and gas limit are sufficient - bytes32 id = gmp.eip712hash(); - vm.expectEmit(true, true, true, true); - emit IGateway.GmpCreated( - id, - GmpSender.unwrap(gmp.source), - gmp.dest, - gmp.destNetwork, - uint64(gmp.gasLimit), - uint64(ctx.value), - gmp.nonce, - gmp.data - ); - console.log("expect: ", ctx.value); - ctx.gasLimit += 17100; - assertEq(ctx.submitMessage(gmp), id, "unexpected GMP id"); - - // Verify the execution cost - assertEq( - ctx.executionCost, - GasUtils.submitMessageGasCost(uint16(gmp.data.length)) + 17100, - "unexpected submit message gas cost" - ); - - // Must revert if fund are insufficient - vm.revertToState(snapshot); - ctx.value -= 1; - vm.expectRevert("insufficient tx value"); - ctx.submitMessage(gmp); - } - - function test_refund() external { - vm.txGasPrice(1); - GmpSender sender = TestUtils.createTestAccount(100 ether).toSender(false); - - // GMP message gas used - uint64 gmpGasUsed = 2_000; - - // Build and sign GMP message - GmpMessage memory gmp = GmpMessage({ - source: sender, - srcNetwork: SRC_NETWORK_ID, - dest: address(receiver), - destNetwork: DEST_NETWORK_ID, - gasLimit: gmpGasUsed, - nonce: 1, - data: abi.encodePacked(uint256(gmpGasUsed)) - }); - Signature memory sig = sign(gmp); - - // Estimate execution cost - (uint256 baseCost, uint256 executionCost) = GatewayUtils.computeGmpGasCost(sig, gmp); - uint256 expectGasUsed = baseCost + executionCost + gmp.gasLimit; - - // Execute GMP message - uint256 beforeBalance = sender.toAddress().balance; - { - CallOptions memory ctx = CallOptions({ - from: sender.toAddress(), - to: address(gateway), - value: 0, - gasLimit: GasUtils.executionGasNeeded(gmp.data.length, gmp.gasLimit) + baseCost, - executionCost: 0, - baseCost: 0 - }); - (GmpStatus status, bytes32 returned) = ctx.execute(sig, gmp); - { - // Verify the gas cost - VmSafe.Gas memory gas = vm.lastCallGas(); - assertEq(gas.gasTotalUsed, executionCost + 2000, "unexpected gas used"); - } - - // Verify the GMP message status - assertEq(uint256(status), uint256(GmpStatus.SUCCESS), "Unexpected GMP status"); - Gateway.GmpInfo memory info = gateway.gmpInfo(gmp.eip712hash()); - assertEq( - uint256(info.status), uint256(GmpStatus.SUCCESS), "GMP status stored doesn't match the returned status" - ); - assertEq(returned, bytes32(uint256(gmp.gasLimit)), "unexpected GMP result"); - - // Verify the gas cost - assertEq(ctx.executionCost + ctx.baseCost, expectGasUsed, "unexpected gas used"); - assertEq(ctx.executionCost, executionCost + gmp.gasLimit, "unexpected execution cost"); - } - - // Verify the gas refund - uint256 afterBalance = sender.toAddress().balance; - assertEq(beforeBalance, afterBalance, "wrong refund amount"); - } - - function test_ExecuteRevertsWrongNetwork() external { - vm.txGasPrice(1); - uint256 amount = 10 ether; - address sender = TestUtils.createTestAccount(amount * 2); - - GmpMessage memory wrongNetwork = GmpMessage({ - source: sender.toSender(false), - srcNetwork: SRC_NETWORK_ID, - dest: address(0x0), - destNetwork: SRC_NETWORK_ID, - gasLimit: 1000, - nonce: 1, - data: "" - }); - Signature memory wrongNetworkSig = sign(wrongNetwork); - CallOptions memory ctx = CallOptions({ - from: sender, - to: address(gateway), - value: 0, - gasLimit: 1_000_000, - executionCost: 0, - baseCost: 0 - }); - vm.expectRevert("invalid gmp network"); - ctx.execute(wrongNetworkSig, wrongNetwork); - } - - function test_ExecuteRevertsBelowGasLimit() external { - vm.txGasPrice(1); - GmpSender sender = TestUtils.createTestAccount(100 ether).toSender(false); - GmpMessage memory gmp = GmpMessage({ - source: sender, - srcNetwork: SRC_NETWORK_ID, - dest: address(receiver), - destNetwork: DEST_NETWORK_ID, - gasLimit: 100_000, - nonce: 1, - data: abi.encode(uint256(100_000)) - }); - Signature memory sig = sign(gmp); - - // Deposit funds - (uint256 baseCost, uint256 executionCost) = GatewayUtils.computeGmpGasCost(sig, gmp); - - // Execute GMP message - CallOptions memory ctx = CallOptions({ - from: sender.toAddress(), - to: address(gateway), - value: 0, - gasLimit: baseCost + executionCost, - executionCost: 0, - baseCost: 0 - }); - vm.expectRevert("insufficient gas to execute GMP message"); - ctx.execute(sig, gmp); - } - - function test_executeRevertsAlreadyExecuted() external { - vm.txGasPrice(1); - GmpSender sender = TestUtils.createTestAccount(1000 ether).toSender(false); - GmpMessage memory gmp = GmpMessage({ - source: sender, - srcNetwork: SRC_NETWORK_ID, - dest: address(receiver), - destNetwork: DEST_NETWORK_ID, - gasLimit: 1000, - nonce: 1, - data: abi.encode(uint256(1000)) - }); - Signature memory sig = sign(gmp); - - // Execute GMP message first time - CallOptions memory ctx = CallOptions({ - from: sender.toAddress(), - to: address(gateway), - value: 0, - gasLimit: 1_000_000, - executionCost: 0, - baseCost: 0 - }); - (GmpStatus status, bytes32 result) = ctx.execute(sig, gmp); - assertEq(uint256(status), uint256(GmpStatus.SUCCESS), "unexpected GMP status"); - assertEq(gmp.gasLimit, uint256(result), "unexpected GMP result"); - - // Execute GMP message second time - vm.expectRevert("message already executed"); - ctx.execute(sig, gmp); - } - - function test_submitGmpMessage() external { - vm.txGasPrice(1); - GmpSender gmpSender = TestUtils.createTestAccount(1000 ether).toSender(false); - GmpMessage memory gmp = GmpMessage({ - source: gmpSender, - srcNetwork: DEST_NETWORK_ID, - dest: address(receiver), - destNetwork: DEST_NETWORK_ID, - gasLimit: 100_000, - nonce: 0, - data: abi.encodePacked(uint256(100_000)) - }); - bytes32 id = gmp.eip712hash(); - - // Check the previous message hash - assertEq(gateway.nonceOf(gmp.source.toAddress()), 0, "wrong previous message hash"); - - CallOptions memory ctx = CallOptions({ - from: gmpSender.toAddress(), - to: address(gateway), - value: 0, - gasLimit: 1_000_000, - executionCost: 0, - baseCost: 0 - }); - - // Compute GMP message price - { - uint16 nonZeros = uint16(GasUtils.countNonZeros(gmp.data)); - uint16 zeros = uint16(gmp.data.length) - nonZeros; - ctx.value = GasUtils.estimateWeiCost(UFloatMath.ONE, 0, nonZeros, zeros, gmp.gasLimit); - } - - // Submit message with insufficient funds - ctx.value -= 1; - vm.expectRevert("insufficient tx value"); - ctx.submitMessage(gmp); - - // Submit message with sufficient funds - ctx.value += 1; - vm.expectEmit(true, true, true, true); - emit IGateway.GmpCreated( - id, - GmpSender.unwrap(gmp.source), - gmp.dest, - gmp.destNetwork, - uint64(gmp.gasLimit), - uint64(ctx.value), - gmp.nonce, - gmp.data - ); - assertEq(ctx.submitMessage(gmp), id, "unexpected GMP id"); - - // Verify the gas cost - uint256 expectedCost = GasUtils.submitMessageGasCost(uint16(gmp.data.length)) - 6500; - assertEq(ctx.executionCost, expectedCost + 17100, "unexpected execution gas cost in first call"); - - // Now the second GMP message nonce must be equals to previous message nonce + 1. - gmp.nonce = gateway.nonceOf(gmp.source.toAddress()); - id = gmp.eip712hash(); - - // Expect event - vm.expectEmit(true, true, true, true); - emit IGateway.GmpCreated( - id, - GmpSender.unwrap(gmp.source), - gmp.dest, - gmp.destNetwork, - uint64(gmp.gasLimit), - uint64(ctx.value), - gmp.nonce, - gmp.data - ); - assertEq(ctx.submitMessage(gmp), id, "unexpected GMP id"); - assertEq(ctx.executionCost, expectedCost - 6800, "unexpected execution gas cost in second call"); - } -} diff --git a/analog-gmp/test/GatewayProxy.t.sol b/analog-gmp/test/GatewayProxy.t.sol deleted file mode 100644 index 5d6c97cac..000000000 --- a/analog-gmp/test/GatewayProxy.t.sol +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (test/Gateway.t.sol) - -pragma solidity >=0.8.0; - -import {IUniversalFactory} from "../lib/universal-factory/src/IUniversalFactory.sol"; -import {FactoryUtils} from "../lib/universal-factory/src/FactoryUtils.sol"; -import {Test, console} from "forge-std/Test.sol"; -import {VmSafe} from "forge-std/Vm.sol"; -import {TestUtils, SigningKey, SigningUtils} from "./TestUtils.sol"; -import {GasSpender} from "./utils/GasSpender.sol"; -import {Gateway, GatewayEIP712} from "../src/Gateway.sol"; -import {GatewayProxy} from "../src/GatewayProxy.sol"; -import {GasUtils} from "../src/utils/GasUtils.sol"; -import {BranchlessMath} from "../src/utils/BranchlessMath.sol"; -import {UFloat9x56, UFloatMath} from "../src/utils/Float9x56.sol"; -import {IGateway} from "../src/interfaces/IGateway.sol"; -import {IGmpReceiver} from "../src/interfaces/IGmpReceiver.sol"; -import {IExecutor} from "../src/interfaces/IExecutor.sol"; -import { - GmpMessage, - UpdateKeysMessage, - Signature, - TssKey, - Network, - GmpStatus, - PrimitiveUtils, - GmpSender -} from "../src/Primitives.sol"; - -contract GatewayProxyTest is Test { - using PrimitiveUtils for UpdateKeysMessage; - using PrimitiveUtils for GmpMessage; - using PrimitiveUtils for GmpSender; - using PrimitiveUtils for address; - using BranchlessMath for uint256; - using SigningUtils for SigningKey; - using FactoryUtils for IUniversalFactory; - - /** - * @dev The address of the `UniversalFactory` contract, must be the same on all networks. - */ - IUniversalFactory private constant FACTORY = IUniversalFactory(0x0000000000001C4Bf962dF86e38F0c10c7972C6E); - // VmSafe.Wallet private proxyAdmin; - // Gateway private gateway; - - // Chronicle TSS Secret - // uint256 private constant ADMIN_SECRET = 0x42; - uint256 private constant SIGNING_NONCE = 0x69; - - // Route IDS - uint16 private constant SRC_NETWORK_ID = 1234; - uint16 private constant DEST_NETWORK_ID = 1337; - - /** - * @dev his is a special contract that implements the IGmpReceiver interface and wastes an exact amount of gas you send to it, helpful - * for testing GMP refunds and gas limits. - * See the file `GasSpender` contract for more details. - */ - IGmpReceiver internal receiver; - - constructor() { - require(FACTORY == TestUtils.deployFactory(), "factory address mismatch"); - receiver = IGmpReceiver(new GasSpender()); - } - - function setUp() external view { - // check block gas limit as gas left - assertEq(block.gaslimit, 30_000_000); - assertTrue(gasleft() >= 10_000_000); - } - - function _setup(VmSafe.Wallet memory admin, bytes32 salt, uint16 routeID) private returns (Gateway gateway) { - /////////////////////////////////////////// - // 1. Deploy the implementation contract // - /////////////////////////////////////////// - // 1.1 Compute the `GatewayProxy` address - bytes memory proxyCreationCode = abi.encodePacked(type(GatewayProxy).creationCode, abi.encode(admin.addr)); - address proxyAddr = FACTORY.computeCreate2Address(salt, proxyCreationCode); - - // 1.2 Deploy the `Gateway` implementation contract - bytes memory implementationCreationCode = - abi.encodePacked(type(Gateway).creationCode, abi.encode(routeID, proxyAddr)); - address payable implementation = payable(FACTORY.create2(salt, implementationCreationCode, abi.encode(routeID))); - assertEq(Gateway(implementation).networkId(), routeID); - - //////////////////////////////////////////////////////// - // 2. ProxyAdmin approves the implementation contract // - //////////////////////////////////////////////////////// - bytes memory authorization; - { - // This allows anyone to deploy the Proxy. - bytes32 digest = keccak256(abi.encode(proxyAddr, address(implementation))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(admin.privateKey, digest); - authorization = abi.encode(v, r, s, address(implementation)); - } - - //////////////////////////////////////////////////////////////// - // 3 - Deploy the `GatewayProxy` using the `UniversalFactory` // - //////////////////////////////////////////////////////////////// - SigningKey memory signer = TestUtils.createSigner(admin.privateKey); - TssKey[] memory keys = new TssKey[](1); - keys[0] = TssKey({yParity: signer.yParity() == 28 ? 1 : 0, xCoord: signer.xCoord()}); // Shard key - Network[] memory networks = new Network[](2); - networks[0].id = SRC_NETWORK_ID; // sepolia network id - networks[0].gateway = proxyAddr; // sepolia proxy address - networks[1].id = DEST_NETWORK_ID; // shibuya network id - networks[1].gateway = proxyAddr; // shibuya proxy address - - // Initializer, used to initialize the Gateway contract - bytes memory initializer = abi.encodeCall(Gateway.initialize, (admin.addr, keys, networks)); - address payable gatewayAddr = payable(FACTORY.create2(salt, proxyCreationCode, authorization, initializer)); - gateway = Gateway(gatewayAddr); - - // Send funds to the gateway contract - vm.deal(address(gateway), 100 ether); - } - - function test_deployProxy() external { - VmSafe.Wallet memory admin = vm.createWallet(vm.randomUint()); - TestUtils.setupGateway(admin, bytes32(uint256(1234)), SRC_NETWORK_ID, DEST_NETWORK_ID); - } -} diff --git a/analog-gmp/test/GmpTestTools.sol b/analog-gmp/test/GmpTestTools.sol deleted file mode 100644 index c00c087c3..000000000 --- a/analog-gmp/test/GmpTestTools.sol +++ /dev/null @@ -1,324 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (test/GmpTestTools.sol) - -pragma solidity >=0.8.0; - -import {VmSafe, Vm} from "forge-std/Vm.sol"; -import {TestUtils, SigningKey, SigningUtils} from "./TestUtils.sol"; -import {Random} from "./Random.sol"; -import {Gateway} from "../src/Gateway.sol"; -import {GatewayProxy} from "../src/GatewayProxy.sol"; -import {IGateway} from "../src/interfaces/IGateway.sol"; -import {BranchlessMath} from "../src/utils/BranchlessMath.sol"; -import {GmpMessage, TssKey, Network, Signature, GmpSender, PrimitiveUtils} from "../src/Primitives.sol"; -import {IUniversalFactory} from "../lib/universal-factory/src/IUniversalFactory.sol"; - -library GmpTestTools { - /** - * @dev Forge Cheat Code VM address, 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D. - */ - address private constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); - Vm private constant vm = Vm(VM_ADDRESS); - - // Sepolia Properties - Gateway internal constant SEPOLIA_GATEWAY = Gateway(payable(0x000000007f56768De3133034fa730a909003A166)); - uint16 internal constant SEPOLIA_NETWORK_ID = 5; - bytes32 internal constant SEPOLIA_SHARD_SECRET = keccak256("analog.sepolia.shard.secret"); - bytes32 internal constant SEPOLIA_DOMAIN_SEPARATOR = keccak256( - abi.encode( - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), - keccak256("Analog Gateway Contract"), - keccak256("0.1.0"), - uint256(SEPOLIA_NETWORK_ID), - address(SEPOLIA_GATEWAY) - ) - ); - - // Shibuya Properties - Gateway internal constant SHIBUYA_GATEWAY = Gateway(payable(0x000000007f56768DE3133034fA730A909003a167)); - uint16 internal constant SHIBUYA_NETWORK_ID = 7; - bytes32 internal constant SHIBUYA_SHARD_SECRET = keccak256("analog.shibuya.shard.secret"); - bytes32 internal constant SHIBUYA_DOMAIN_SEPARATOR = keccak256( - abi.encode( - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), - keccak256("Analog Gateway Contract"), - keccak256("0.1.0"), - uint256(SHIBUYA_NETWORK_ID), - address(SHIBUYA_GATEWAY) - ) - ); - - /** - * @dev Minimal Eip1667 proxy bytecode. - */ - bytes private constant _PROXY_BYTECODE = - hex"363d3d373d3d3d363d7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af43d82803e903d91603857fd5bf3"; - - /** - * @dev Storage slot with the address of the current implementation. - * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. - */ - bytes32 private constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - - /** - * @dev Log index storage slot. - * This prevents a given message from being execute more than once. - */ - bytes32 private constant _LOG_INDEX_SLOT = bytes32(uint256(keccak256("analog.GmpTestTools.logIndex")) - 1); - - /** - * @dev Mapping of network ID to fork ID. - */ - bytes32 private constant _FORKS_SLOT = bytes32(uint256(keccak256("analog.GmpTestTools.forks")) - 1); - - /** - * @dev Storage slot. - */ - function setup() internal { - require(vm.isPersistent(address(this)), "GmpTestTools must only be called from Test contract"); - // Create forks - uint256 sepoliaForkID = vm.createFork("https://sepolia.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001", 5714300); - uint256 shibuyaForkID = vm.createFork("https://evm.shibuya.astar.network", 6102790); - - // Save the fork IDs - _storeForkID(SEPOLIA_NETWORK_ID, sepoliaForkID); - _storeForkID(SHIBUYA_NETWORK_ID, shibuyaForkID); - - // Deploy the gateways - Network[] memory networks = new Network[](2); - networks[0] = Network({id: SEPOLIA_NETWORK_ID, gateway: address(SEPOLIA_GATEWAY)}); - networks[1] = Network({id: SHIBUYA_NETWORK_ID, gateway: address(SHIBUYA_GATEWAY)}); - - // Setup the networks - require(switchNetwork(SEPOLIA_NETWORK_ID) == sepoliaForkID, "unexpected sepolia fork id"); - setupNetwork(SEPOLIA_NETWORK_ID, address(SEPOLIA_GATEWAY), SEPOLIA_SHARD_SECRET, networks); - - require(switchNetwork(SHIBUYA_NETWORK_ID) == shibuyaForkID, "unexpected shibuya fork id"); - setupNetwork(SHIBUYA_NETWORK_ID, address(SHIBUYA_GATEWAY), SHIBUYA_SHARD_SECRET, networks); - - // Record logs must be enabled to allow this tool to retrieve the GMP messages - vm.recordLogs(); - } - - function setupNetwork(uint16 networkId, address gateway, bytes32 secret, Network[] memory networks) internal { - // Deploy `Universal Factory` contract - IUniversalFactory factory = TestUtils.deployFactory(); - - SigningKey memory signer = TestUtils.signerFromEntropy(secret); - TssKey[] memory keys = new TssKey[](1); - keys[0] = TssKey({yParity: uint8(signer.pubkey.py % 2), xCoord: signer.pubkey.px}); - - // Check if the gateway is already deployed - bool exists = gateway.code.length > 0; - - // Deploy the gateway proxy - bytes memory implementationCreationCode = abi.encodePacked(type(Gateway).creationCode, abi.encode(gateway)); - - // address implementation = address(new Gateway(networkId, gateway)); - address implementation; - bytes32 salt = bytes32(0); - if (exists) { - implementation = factory.create2(salt, implementationCreationCode, ""); - } else { - implementation = factory.create2(salt, implementationCreationCode, abi.encode(networkId, address(gateway))); - } - vm.etch(gateway, _PROXY_BYTECODE); - vm.store(gateway, _IMPLEMENTATION_SLOT, bytes32(uint256(uint160(implementation)))); - - // If the gateway is already deployed, just register the shard - // This is useful when using forked networks - if (exists) { - bytes32 prevMessageHash = vm.load(gateway, bytes32(uint256(4))); - if (prevMessageHash != bytes32(0)) { - registerShard(gateway, signer); - revert("ALREADY INITIALIZED"); - } - } - - // Change caller mode because only the gateway can initialize itself - (VmSafe.CallerMode callerMode, address msgSender, address txOrigin) = - TestUtils.setCallerMode(VmSafe.CallerMode.Prank, gateway, gateway); - - // Initialize the gateway - Gateway(payable(gateway)).initialize(msgSender, keys, networks); - - // Restore previous caller mode - TestUtils.setCallerMode(callerMode, msgSender, txOrigin); - } - - function deal(address account, uint256 newBalance) internal { - // If the account is persistent, just need to deal once - if (vm.isPersistent(account)) { - vm.deal(account, newBalance); - return; - } - // Select sepolia and execute callback - switchNetwork(SEPOLIA_NETWORK_ID); - vm.deal(account, newBalance); - - // Select shibuya and execute callback - switchNetwork(SHIBUYA_NETWORK_ID); - vm.deal(account, newBalance); - } - - /** - * @dev Execute all pending GMP messages. - */ - function relayMessages() internal { - uint256 activeFork = vm.activeFork(); - GmpMessage[] memory allMessages = messages(); - _executeMessages(SHIBUYA_GATEWAY, SHIBUYA_NETWORK_ID, SHIBUYA_SHARD_SECRET, allMessages); - _executeMessages(SEPOLIA_GATEWAY, SEPOLIA_NETWORK_ID, SEPOLIA_SHARD_SECRET, allMessages); - vm.selectFork(activeFork); - } - - /** - * @dev Switch to `network` fork id. - */ - function switchNetwork(uint16 network) internal returns (uint256 forkId) { - forkId = _loadForkID(network); - require(forkId != uint256(_FORKS_SLOT), "GmpTestTools: network not found"); - vm.selectFork(forkId); - } - - /** - * @dev Switch to the `network` fork id and sets all subsequent calls' `msg.sender` to `msgSender`. - */ - function switchNetwork(uint16 network, address msgSender) internal returns (uint256 forkId) { - forkId = switchNetwork(network); - vm.stopPrank(); - vm.startPrank(msgSender, msgSender); - } - - /** - * @dev Stores the network fork id. - */ - function _storeForkID(uint16 network, uint256 forkId) private { - bytes32 slot = _deriveMapping(_FORKS_SLOT, uint256(network)); - // Once zero is a valid fork id, we XOR before storing to prevent - // an invalid network from returning a valid fork id - _sstoreUint256(slot, forkId ^ uint256(_FORKS_SLOT)); - } - - /** - * @dev Load the fork id of a given `network` - */ - function _loadForkID(uint16 network) private view returns (uint256) { - bytes32 slot = _deriveMapping(_FORKS_SLOT, uint256(network)); - // Once zero is a valid fork id, we XOR the returned result to prevent - // an invalid network from returning a valid fork id - return _sloadUint256(slot) ^ uint256(_FORKS_SLOT); - } - - /** - * @dev Returns the `uint256` located at `slot`. - */ - function _sloadUint256(bytes32 slot) private view returns (uint256 r) { - assembly { - r := sload(slot) - } - } - - /** - * @dev Store `value` at `slot`. - */ - function _sstoreUint256(bytes32 slot, uint256 value) private { - assembly { - sstore(slot, value) - } - } - - /** - * @dev Force register a new shard in the gateway. - */ - function registerShard(address gateway, SigningKey memory shard) internal { - // uint256 shardInfo = 1 | (shard.pubkey.py % 2); - bytes32 slot = _deriveMapping(bytes32(0), shard.pubkey.px); - uint256 shardInfo = uint256(vm.load(gateway, slot)); - uint256 nonce = shardInfo >> 224; - nonce = BranchlessMath.ternary(nonce > 0, nonce, 1); - shardInfo = (nonce << 224) | (1 << 216) | ((shard.pubkey.py % 2) << 217); - vm.store(gateway, slot, bytes32(shardInfo)); - } - - /** - * @dev Derive the location of a mapping element from the key. - */ - function _deriveMapping(bytes32 slot, uint256 key) private pure returns (bytes32 result) { - assembly ("memory-safe") { - mstore(0x00, key) - mstore(0x20, slot) - result := keccak256(0x00, 0x40) - } - } - - /** - * @dev Retrieve all pending messages from the recorded logs - */ - function messages() internal returns (GmpMessage[] memory gmpMessages) { - bytes32[] memory topics = new bytes32[](1); - topics[0] = IGateway.GmpCreated.selector; - Vm.Log[] memory logs = vm.getRecordedLogs(); - uint256 logIndex = _sloadUint256(_LOG_INDEX_SLOT); - gmpMessages = new GmpMessage[](logs.length - logIndex); - uint256 pos = 0; - for (uint256 i = logIndex; i < logs.length; i++) { - Vm.Log memory log = logs[i]; - - // Filter emitters - uint16 srcNetwork; - if (log.emitter == address(SEPOLIA_GATEWAY)) { - srcNetwork = SEPOLIA_NETWORK_ID; - } else if (log.emitter == address(SHIBUYA_GATEWAY)) { - srcNetwork = SHIBUYA_NETWORK_ID; - } else { - continue; - } - - // Filter topics - if (log.topics.length != 4 || log.topics[0] != IGateway.GmpCreated.selector) { - continue; - } - - // Decode the GMP message - (uint16 destNetwork, uint64 gasLimit, uint64 nonce, bytes memory data) = - abi.decode(log.data, (uint16, uint64, uint64, bytes)); - gmpMessages[pos++] = GmpMessage({ - source: GmpSender.wrap(log.topics[2]), - srcNetwork: srcNetwork, - dest: address(uint160(uint256(log.topics[3]))), - destNetwork: destNetwork, - gasLimit: gasLimit, - nonce: nonce, - data: data - }); - } - _sstoreUint256(_LOG_INDEX_SLOT, logs.length); - } - - function _executeMessages(Gateway gateway, uint16 network, bytes32 secret, GmpMessage[] memory gmpMessages) - private - { - switchNetwork(network); - SigningKey memory signer = TestUtils.signerFromEntropy(secret); - - for (uint256 i = 0; i < gmpMessages.length; i++) { - GmpMessage memory message = gmpMessages[i]; - - // Compute the message ID - bytes32 messageID = PrimitiveUtils.eip712hash(message); - - // Skip if the message is not intended for this network - if (message.destNetwork != network) { - continue; - } - - // Sign the message - (uint256 c, uint256 z) = SigningUtils.signPrehashed(signer, messageID, Random.nextUint()); - Signature memory signature = Signature({xCoord: signer.pubkey.px, e: c, s: z}); - - // Execute the message - gateway.execute(signature, message); - } - } -} diff --git a/analog-gmp/test/GmpTestTools.t.sol b/analog-gmp/test/GmpTestTools.t.sol deleted file mode 100644 index 7cd96cc0c..000000000 --- a/analog-gmp/test/GmpTestTools.t.sol +++ /dev/null @@ -1,129 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (test/GmpTestTools.t.sol) - -pragma solidity >=0.8.0; - -import {Test} from "forge-std/Test.sol"; -import {MockERC20} from "./MockERC20.sol"; -import {TestUtils} from "./TestUtils.sol"; -import {GmpTestTools} from "./GmpTestTools.sol"; -import {Gateway, GatewayEIP712} from "../src/Gateway.sol"; -import {GatewayProxy} from "../src/GatewayProxy.sol"; -import {IGateway} from "../src/interfaces/IGateway.sol"; -import {IExecutor} from "../src/interfaces/IExecutor.sol"; -import {GmpMessage, GmpStatus, GmpSender, PrimitiveUtils} from "../src/Primitives.sol"; - -contract GmpTestToolsTest is Test { - using PrimitiveUtils for GmpMessage; - using PrimitiveUtils for GmpSender; - using PrimitiveUtils for address; - - address private constant ALICE = address(bytes20(keccak256("Alice"))); - address private constant BOB = address(bytes20(keccak256("Bob"))); - - Gateway private constant SEPOLIA_GATEWAY = Gateway(GmpTestTools.SEPOLIA_GATEWAY); - uint16 private constant SEPOLIA_NETWORK = GmpTestTools.SEPOLIA_NETWORK_ID; - - Gateway private constant SHIBUYA_GATEWAY = Gateway(GmpTestTools.SHIBUYA_GATEWAY); - uint16 private constant SHIBUYA_NETWORK = GmpTestTools.SHIBUYA_NETWORK_ID; - - /// @dev Test the teleport of tokens from Alice's account in Shibuya to Bob's account in Sepolia - function testTeleportAliceTokens() external { - //////////////////////////////////// - // Step 1: Setup test environment // - //////////////////////////////////// - - if (msg.data.length > 0) { - return; - } - - // Deploy the gateway contracts at pre-defined addresses - // Also creates one fork for each supported network - GmpTestTools.setup(); - - // Add funds to Alice and Bob in all networks - GmpTestTools.deal(ALICE, 100 ether); - GmpTestTools.deal(BOB, 100 ether); - - /////////////////////////////////////////////////////// - // Step 2: Deploy the sender and recipient contracts // - /////////////////////////////////////////////////////// - - // Pre-compute the contract addresses, because the contracts must know each other addresses. - MockERC20 shibuyaErc20 = MockERC20(vm.computeCreateAddress(ALICE, vm.getNonce(ALICE))); - MockERC20 sepoliaErc20 = MockERC20(vm.computeCreateAddress(BOB, vm.getNonce(BOB))); - - // Switch to Shibuya network and deploy the ERC20 using Alice account - GmpTestTools.switchNetwork(SHIBUYA_NETWORK, ALICE); - shibuyaErc20 = new MockERC20("Shibuya ", "A", SHIBUYA_GATEWAY, sepoliaErc20, SEPOLIA_NETWORK, ALICE, 1000); - assertEq(shibuyaErc20.balanceOf(ALICE), 1000, "unexpected alice balance in shibuya"); - assertEq(shibuyaErc20.balanceOf(BOB), 0, "unexpected bob balance in shibuya"); - - // Switch to Sepolia network and deploy the ERC20 using Bob account - GmpTestTools.switchNetwork(SEPOLIA_NETWORK, BOB); - sepoliaErc20 = new MockERC20("Sepolia", "B", SEPOLIA_GATEWAY, shibuyaErc20, SHIBUYA_NETWORK, BOB, 0); - assertEq(sepoliaErc20.balanceOf(ALICE), 0, "unexpected alice balance in sepolia"); - assertEq(sepoliaErc20.balanceOf(BOB), 0, "unexpected bob balance in sepolia"); - - // Check if the computed addresses matches - assertEq(address(shibuyaErc20), vm.computeCreateAddress(ALICE, 0), "unexpected shibuyaErc20 address"); - assertEq(address(sepoliaErc20), vm.computeCreateAddress(BOB, 0), "unexpected sepoliaErc20 address"); - - /////////////////////////////////////////////////////////// - // Step 3: Deposit funds to destination Gateway Contract // - /////////////////////////////////////////////////////////// - - // Switch to Sepolia network and Alice account - GmpTestTools.switchNetwork(SEPOLIA_NETWORK, ALICE); - - ////////////////////////////// - // Step 4: Send GMP message // - ////////////////////////////// - - // Switch to Shibuya network and Alice account - GmpTestTools.switchNetwork(SHIBUYA_NETWORK, ALICE); - - // Teleport 100 tokens from Alice to to Bob's account in sepolia - // Obs: The `teleport` method internally calls `gateway.submitMessage(...)` - bytes32 messageID; - { - // Estimate the cost of teleporting 100 tokens - uint256 gmpCost = shibuyaErc20.teleportCost(); - messageID = shibuyaErc20.teleport{value: gmpCost}(BOB, 100); - } - - // Now with the `messageID`, Alice can check the message status in the destination gateway contract - // status 0: means the message is pending - // status 1: means the message was executed successfully - // status 2: means the message was executed but reverted - GmpTestTools.switchNetwork(SEPOLIA_NETWORK, ALICE); - assertTrue( - SEPOLIA_GATEWAY.gmpInfo(messageID).status == GmpStatus.NOT_FOUND, - "unexpected message status, expect 'pending'" - ); - - /////////////////////////////////////////////////// - // Step 5: Wait Chronicles Relay the GMP message // - /////////////////////////////////////////////////// - - // The GMP hasn't been executed yet... - assertEq(sepoliaErc20.balanceOf(ALICE), 0, "unexpected alice balance in shibuya"); - - // Note: In a live network, the GMP message will be relayed by Chronicle Nodes after a minimum number of confirmations. - // here we can simulate this behavior by calling `GmpTestTools.relayMessages()`, this will relay all pending messages. - GmpTestTools.relayMessages(); - - // Success! The GMP message was executed!!! - assertTrue(SEPOLIA_GATEWAY.gmpInfo(messageID).status == GmpStatus.SUCCESS, "failed to execute GMP"); - - // Check ALICE and BOB balance in shibuya - GmpTestTools.switchNetwork(SHIBUYA_NETWORK); - assertEq(shibuyaErc20.balanceOf(ALICE), 900, "unexpected alice's balance in shibuya"); - assertEq(shibuyaErc20.balanceOf(BOB), 0, "unexpected bob's balance in shibuya"); - - // Check ALICE and BOB balance in sepolia - GmpTestTools.switchNetwork(SEPOLIA_NETWORK); - assertEq(sepoliaErc20.balanceOf(ALICE), 0, "unexpected alice's balance in sepolia"); - assertEq(sepoliaErc20.balanceOf(BOB), 100, "unexpected bob's balance in sepolia"); - } -} diff --git a/analog-gmp/test/MockERC20.sol b/analog-gmp/test/MockERC20.sol deleted file mode 100644 index 524645cfc..000000000 --- a/analog-gmp/test/MockERC20.sol +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (test/MockERC20.sol) - -pragma solidity >=0.8.0; - -import {ERC20} from "../lib/solmate/src/tokens/ERC20.sol"; -import {IGmpReceiver} from "../src/interfaces/IGmpReceiver.sol"; -import {IGateway} from "../src/interfaces/IGateway.sol"; - -contract MockERC20 is ERC20, IGmpReceiver { - IGateway private immutable _gateway; - MockERC20 private immutable _recipientErc20; - uint16 private immutable _recipientNetwork; - - // Gas limit used to execute `onGmpReceived` method. - uint256 private constant MSG_GAS_LIMIT = 100_000; - - /** - * @dev Struct to represent a cross-chain transfer message. - * @param from The sender address. - * @param to The recipient address. - * @param amount The amount of tokens to teleport. - */ - struct CrossChainTransfer { - address from; - address to; - uint256 amount; - } - - constructor( - string memory name, - string memory symbol, - IGateway gatewayAddress, - MockERC20 recipient, - uint16 recipientNetwork, - address holder, - uint256 initialSupply - ) ERC20(name, symbol, 10) { - _gateway = gatewayAddress; - _recipientErc20 = recipient; - _recipientNetwork = recipientNetwork; - if (initialSupply > 0) { - _mint(holder, initialSupply); - } - } - - /** - * @dev Estimate the cost of teleporting tokens to another network. - */ - function teleportCost() external view returns (uint256) { - // Estimate the cost - return _gateway.estimateMessageCost(_recipientNetwork, 96, MSG_GAS_LIMIT); - } - - /** - * @dev Teleport tokens to from this contract to another contract on a different network. - * IMPORTANT: the caller is responsible to compute the teleport cost and send the required amount of ETH. - * The teleport cost can be computed using the `teleportCost` method. - * - * @param to The recipient address on the destination network. - * @param amount The amount of tokens to teleport. - */ - function teleport(address to, uint256 amount) external payable returns (bytes32) { - // Encode the message - bytes memory message = abi.encode(CrossChainTransfer({from: msg.sender, to: to, amount: amount})); - - // Burn the tokens - _burn(msg.sender, amount); - - // Submit the GMP message - return _gateway.submitMessage{value: msg.value}( - address(_recipientErc20), _recipientNetwork, MSG_GAS_LIMIT, message - ); - } - - function onGmpReceived(bytes32 id, uint128 network, bytes32 sender, uint64, bytes calldata data) - external - payable - returns (bytes32) - { - require(msg.sender == address(_gateway), "Unauthorized: only the gateway can call this method"); - require(network == _recipientNetwork, "Unauthorized network"); - require(address(uint160(uint256(sender))) == address(_recipientErc20), "Unauthorized sender"); - CrossChainTransfer memory message = abi.decode(data, (CrossChainTransfer)); - _mint(message.to, message.amount); - return id; - } -} diff --git a/analog-gmp/test/Random.sol b/analog-gmp/test/Random.sol deleted file mode 100644 index 6b1403c6a..000000000 --- a/analog-gmp/test/Random.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (test/Random.sol) - -pragma solidity >=0.8.0; - -/** - * @dev Utilities for generating pseudo-random values - */ -library Random { - function _next() private pure returns (uint256 rand) { - assembly ("memory-safe") { - rand := keccak256(0x00, 0x60) - let ptr := mload(0x40) - mstore(0x00, xor(rand, calldataload(0))) - mstore(0x20, xor(rand, calldatasize())) - mstore(0x40, xor(rand, mload(ptr))) - rand := keccak256(0x00, 0x60) - mstore(0x00, rand) - mstore(0x20, rand) - mstore(0x40, ptr) - mstore(ptr, rand) - } - } - - function nextUint() internal pure returns (uint256 rand) { - rand = _next(); - } - - function nextInt() internal pure returns (int256 rand) { - rand = int256(_next()); - } -} diff --git a/analog-gmp/test/TestUtils.sol b/analog-gmp/test/TestUtils.sol deleted file mode 100644 index 6ec189582..000000000 --- a/analog-gmp/test/TestUtils.sol +++ /dev/null @@ -1,542 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (test/TestUtils.sol) - -pragma solidity >=0.8.0; - -import {VmSafe, Vm} from "forge-std/Vm.sol"; -import {console} from "forge-std/console.sol"; -import {Schnorr} from "../lib/frost-evm/sol/Schnorr.sol"; -import {SECP256K1} from "../lib/frost-evm/sol/SECP256K1.sol"; -import {BranchlessMath} from "../src/utils/BranchlessMath.sol"; -import {IUniversalFactory} from "../lib/universal-factory/src/IUniversalFactory.sol"; -import {FactoryUtils} from "../lib/universal-factory/src/FactoryUtils.sol"; -import {IGateway} from "../src/interfaces/IGateway.sol"; -import {Gateway, GatewayEIP712} from "../src/Gateway.sol"; -import {GatewayProxy} from "../src/GatewayProxy.sol"; -import { - GmpMessage, - UpdateKeysMessage, - Signature, - TssKey, - Network, - GmpStatus, - PrimitiveUtils, - GmpSender -} from "../src/Primitives.sol"; - -struct VerifyingKey { - uint256 px; - uint256 py; -} - -struct SigningKey { - uint256 secret; - VerifyingKey pubkey; -} - -/** - * @dev Utilities for testing purposes - */ -library TestUtils { - using BranchlessMath for uint256; - using FactoryUtils for IUniversalFactory; - - // Cheat code address, 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D. - address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); - Vm internal constant vm = Vm(VM_ADDRESS); - - /** - * @dev The address of the `UniversalFactory` contract, must be the same on all networks. - */ - address internal constant FACTORY_DEPLOYER = 0x908064dE91a32edaC91393FEc3308E6624b85941; - - /** - * @dev The codehash of the `UniversalFactory` contract, must be the same on all networks. - */ - bytes32 internal constant FACTORY_CODEHASH = 0x0dac89b851eaa2369ef725788f1aa9e2094bc7819f5951e3eeaa28420f202b50; - - /** - * @dev The address of the `UniversalFactory` contract, must be the same on all networks. - */ - IUniversalFactory internal constant FACTORY = IUniversalFactory(0x0000000000001C4Bf962dF86e38F0c10c7972C6E); - - /** - * @dev Deploys a contract with the given bytecode - */ - function deployContract(bytes memory bytecode) internal returns (address addr) { - require(bytecode.length > 0, "Error: deploy code is empty"); - assembly ("memory-safe") { - let ptr := add(bytecode, 32) - let size := mload(bytecode) - addr := create(0, ptr, size) - } - require(addr != address(0), "Error: failed to deploy contract"); - } - - /** - * @dev Delegate call to another contract bytecode - * This execute the code of another contract in the context of the current contract - */ - function delegateCall(address contractAddr, bytes memory data) - internal - returns (bool success, bytes memory output) - { - require(contractAddr.code.length > 0, "Error: provided address is not a contract"); - assembly ("memory-safe") { - success := - delegatecall( - gas(), // call gas limit - contractAddr, // dest address - add(32, data), // input memory pointer - mload(data), // input size - 0, // output memory pointer - 0 // output size - ) - - // Alloc memory for the output - output := mload(0x40) - let ptr := add(output, 32) - let size := returndatasize() - mstore(0x40, add(ptr, size)) // Increment free memory pointer - - // Store return data size - mstore(output, size) - - // Copy delegatecall output to memory - returndatacopy(ptr, 0, size) - } - } - - /** - * @dev Count the number of non-zero bytes in a byte sequence. - * Reference: https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel - */ - function countNonZeros(bytes memory data) internal pure returns (uint256 nonZeros) { - assembly ("memory-safe") { - // Efficient algorithm for counting non-zero bytes in parallel - nonZeros := 0 - for { - // 32 byte aligned pointer, ex: if data.length is 54, `ptr` starts at 32 - let ptr := add(data, and(add(mload(data), 31), 0xffffffe0)) - } gt(ptr, data) { ptr := sub(ptr, 32) } { - // Normalize - let v := mload(ptr) - v := or(v, shr(4, v)) - v := or(v, shr(2, v)) - v := or(v, shr(1, v)) - v := and(v, 0x0101010101010101010101010101010101010101010101010101010101010101) - - // Count bytes in parallel - v := add(v, shr(128, v)) - v := add(v, shr(64, v)) - v := add(v, shr(32, v)) - v := add(v, shr(16, v)) - v := add(v, shr(8, v)) - v := and(v, 0xff) - nonZeros := add(nonZeros, v) - } - } - } - - /** - * @dev Calculate the tx base cost. - * formula: 21000 + zeros * 4 + nonZeros * 16 - * Reference: https://eips.ethereum.org/EIPS/eip-2028 - */ - function calculateBaseCost(bytes memory txData) internal pure returns (uint256 baseCost) { - uint256 nonZeros = countNonZeros(txData); - uint256 zeros = txData.length - nonZeros; - baseCost = 21_000 + (nonZeros * 16) + (zeros * 4); - } - - /** - * @dev Calculate the tx base cost. - * formula: 21000 + zeros * 4 + nonZeros * 16 - * Reference: https://eips.ethereum.org/EIPS/eip-2028 - */ - function memExpansionCost(uint256 size) internal pure returns (uint256) { - uint256 words = (size + 31) / 32; - return ((words ** 2) / 512) + (words * 3); - } - - /** - * @dev Generate a new account account from the calldata - * This will generate a unique deterministic address for each test case - */ - function createTestAccount(uint256 initialBalance) internal returns (address account) { - // Generate a new account address from the calldata - // This will generate a unique deterministic address for each test case - account = address(uint160(uint256(keccak256(msg.data)))); - vm.deal(account, initialBalance); - } - - /** - * @dev Generate a new account account from the calldata - */ - function createTestAccount() internal returns (address account) { - // Create an account with 100 ether - account = createTestAccount(100 ether); - } - - /** - * @dev Convert an address to GMP bytes32 identifier - */ - function source(address account, bool isContract) internal pure returns (bytes32) { - uint256 contractFlag = isContract ? 1 << 160 : 0; - return bytes32(contractFlag | uint256(uint160(account))); - } - - /** - * @dev Convert an address to GMP bytes32 identifier - */ - function source(address account) internal view returns (bytes32) { - return source(account, account.code.length > 0); - } - - /** - * @dev Creates a new TSS signer - */ - function createSigner(uint256 secret) internal pure returns (SigningKey memory) { - require(secret != 0, "secret must be greater than 0"); - require(secret < Schnorr.Q, "secret must be less than secp256k1 group order"); - (uint256 px, uint256 py) = SECP256K1.publicKey(secret); - return SigningKey({secret: secret, pubkey: VerifyingKey({px: px, py: py})}); - } - - /** - * @dev Creates a new TSS signer - */ - function signerFromEntropy(bytes32 entropy) internal pure returns (SigningKey memory) { - uint256 secret; - assembly { - mstore(0, entropy) - secret := keccak256(0x00, 0x20) - } - while (secret >= Schnorr.Q) { - assembly { - mstore(0, secret) - secret := keccak256(0x00, 0x20) - } - } - return createSigner(secret); - } - - /** - * @dev Creates an unique TSS signer per test case - */ - function createSigner() internal pure returns (SigningKey memory) { - return signerFromEntropy(keccak256(msg.data)); - } - - // Workaround for set the tx.gasLimit, currently is not possible to define the gaslimit in foundry - // Reference: https://github.com/foundry-rs/foundry/issues/2224 - function _call(address addr, uint256 gasLimit, uint256 value, bytes memory data) - private - returns (uint256 gasUsed, bool success, bytes memory out) - { - require(gasleft() > gasLimit.saturatingAdd(5000), "insufficient gas"); - require(addr.code.length > 0, "Not a contract address"); - assembly ("memory-safe") { - success := - call( - 0x7E7E7E7E7E7E7E7E7E7E7E7E7E7E7E7E7E7E7E7E7E7E7E7E7E7E7E7E7E7E7E, // Code Injection TAG - gasLimit, // gas limit - addr, // addr - value, // value - add(data, 32), // arg offset - mload(data), // arg size - 0 // return offset - ) - gasUsed := mload(0) - - out := mload(0x40) - let size := returndatasize() - mstore(out, size) - let ptr := add(out, 32) - returndatacopy(ptr, 0, size) - mstore(0x40, add(ptr, size)) - } - } - - // Execute a contract call and calculate the acurrate execution gas cost - function executeCall(address sender, address dest, uint256 gasLimit, uint256 value, bytes memory data) - internal - returns (uint256 executionCost, uint256 baseCost, bytes memory out) - { - bool success; - (executionCost, baseCost, success, out) = tryExecuteCall(sender, dest, gasLimit, value, data); - // Revert if the execution failed - assembly { - if iszero(success) { revert(add(out, 32), mload(out)) } - } - } - - // Execute a contract call and calculate the acurrate execution gas cost - function tryExecuteCall(address sender, address dest, uint256 gasLimit, uint256 value, bytes memory data) - internal - returns (uint256 executionCost, uint256 baseCost, bool success, bytes memory out) - { - // Guarantee there's enough gas to execute the call - { - uint256 gasRequired = (gasLimit * 64) / 63; - gasRequired += 50_000; - require(gasleft() > gasRequired, "insufficient gas left to execute call"); - } - - // Compute the base tx cost (21k + 4 * zeros + 16 * nonZeros) - baseCost = calculateBaseCost(data); - - // Decrement sender base cost and value - { - uint256 txFees = gasLimit.saturatingMul(tx.gasprice); - require(sender.balance >= txFees.saturatingAdd(value), "account has no sufficient funds"); - vm.deal(sender, sender.balance - txFees); - gasLimit = gasLimit.saturatingSub(baseCost); - } - - // Execute - { - (VmSafe.CallerMode callerMode, address msgSender, address txOrigin) = - setCallerMode(VmSafe.CallerMode.RecurrentPrank, sender, sender); - (executionCost, success, out) = _call(dest, gasLimit, value, data); - setCallerMode(callerMode, msgSender, txOrigin); - } - - // Refund unused gas - uint256 refund = gasLimit.saturatingSub(executionCost).saturatingMul(tx.gasprice); - if (refund > 0) { - vm.deal(sender, sender.balance + refund); - } - } - - function setCallerMode(VmSafe.CallerMode callerMode, address msgSender, address txOrigin) - internal - returns (VmSafe.CallerMode prevCallerMode, address prevMsgSender, address prevTxOrigin) - { - (prevCallerMode, prevMsgSender, prevTxOrigin) = vm.readCallers(); - - // Stop previous caller mode - if (prevCallerMode == VmSafe.CallerMode.RecurrentBroadcast) { - vm.stopBroadcast(); - } else if (prevCallerMode == VmSafe.CallerMode.RecurrentPrank) { - vm.stopPrank(); - } - - // Set new caller mode - if (callerMode == VmSafe.CallerMode.Broadcast) { - vm.broadcast(msgSender); - } else if (callerMode == VmSafe.CallerMode.RecurrentBroadcast) { - vm.startBroadcast(msgSender); - } else if (callerMode == VmSafe.CallerMode.Prank) { - vm.prank(msgSender, txOrigin); - } else if (callerMode == VmSafe.CallerMode.RecurrentPrank) { - vm.startPrank(msgSender, txOrigin); - } - } - - function prank(address msgSender, address txOrigin, function() f) internal { - VmSafe.CallerMode callerMode = VmSafe.CallerMode.RecurrentPrank; - (callerMode, msgSender, txOrigin) = setCallerMode(VmSafe.CallerMode.RecurrentPrank, msgSender, txOrigin); - f(); - setCallerMode(callerMode, msgSender, txOrigin); - } - - function prank(address msgSender, function() f) internal { - VmSafe.CallerMode callerMode = VmSafe.CallerMode.RecurrentPrank; - address txOrigin = msgSender; - (callerMode, msgSender, txOrigin) = setCallerMode(VmSafe.CallerMode.RecurrentPrank, msgSender, txOrigin); - f(); - setCallerMode(callerMode, msgSender, txOrigin); - } - - function deployFactory() internal returns (IUniversalFactory) { - // Check if the factory is already deployed - if (address(FACTORY).code.length > 0) { - bytes32 codehash; - address addr = address(FACTORY); - assembly { - codehash := extcodehash(addr) - } - require(codehash == FACTORY_CODEHASH, "Invalid factory codehash"); - return FACTORY; - } - - uint256 nonce = vm.getNonce(FACTORY_DEPLOYER); - require(nonce == 0, "Factory deployer account has already been used"); - - bytes memory creationCode = vm.getCode("./lib/universal-factory/abi/UniversalFactory.json"); - vm.deal(FACTORY_DEPLOYER, 100 ether); - vm.prank(FACTORY_DEPLOYER, FACTORY_DEPLOYER); - address factory; - assembly { - factory := create(0, add(creationCode, 32), mload(creationCode)) - } - require(factory == address(FACTORY), "Factory address mismatch"); - require(keccak256(factory.code) == FACTORY_CODEHASH, "Factory codehash mismatch"); - return FACTORY; - } - - /** - * @dev Deploy a new Gateway and GatewayProxy contracts. - */ - function computeGatewayProxyAddress(address admin, bytes32 salt) internal pure returns (address) { - // 1.1 Compute the `GatewayProxy` address - bytes memory proxyCreationCode = abi.encodePacked(type(GatewayProxy).creationCode, abi.encode(admin)); - return FACTORY.computeCreate2Address(salt, proxyCreationCode); - } - - /** - * @dev Deploy a new Gateway and GatewayProxy contracts. - */ - function setupGateway( - VmSafe.Wallet memory admin, - bytes32 salt, - uint16 routeId, - TssKey[] memory keys, - Network[] memory networks - ) internal returns (IGateway gateway) { - require(FACTORY == TestUtils.deployFactory(), "UniversalFactory not deployed"); - - /////////////////////////////////////////// - // 1. Deploy the implementation contract // - /////////////////////////////////////////// - // 1.1 Compute the `GatewayProxy` address - address proxyAddr = computeGatewayProxyAddress(admin.addr, salt); - - // 1.2 Deploy the `Gateway` implementation contract - bytes memory implementationCreationCode = - abi.encodePacked(type(Gateway).creationCode, abi.encode(routeId, proxyAddr)); - address implementation = FACTORY.create2(salt, implementationCreationCode, abi.encode(routeId)); - - //////////////////////////////////////////////////////// - // 2. ProxyAdmin approves the implementation contract // - //////////////////////////////////////////////////////// - bytes memory authorization; - { - // This allows anyone to deploy the Proxy. - bytes32 digest = keccak256(abi.encode(proxyAddr, address(implementation))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(admin.privateKey, digest); - authorization = abi.encode(v, r, s, address(implementation)); - } - - //////////////////////////////////////////////////////////////// - // 3 - Deploy the `GatewayProxy` using the `UniversalFactory` // - //////////////////////////////////////////////////////////////// - // Initializer, used to initialize the Gateway contract - bytes memory initializer = abi.encodeCall(Gateway.initialize, (admin.addr, keys, networks)); - bytes memory proxyCreationCode = abi.encodePacked(type(GatewayProxy).creationCode, abi.encode(admin.addr)); - address payable gatewayAddr = payable(FACTORY.create2(salt, proxyCreationCode, authorization, initializer)); - gateway = Gateway(gatewayAddr); - - // Send funds to the gateway contract - vm.deal(address(gateway), 100 ether); - } - - /** - * @dev Deploy a new Gateway and GatewayProxy contracts. - */ - function setupGateway(VmSafe.Wallet memory admin, bytes32 salt, uint16 srcRoute, uint16 dstRoute) - internal - returns (IGateway gateway) - { - require(FACTORY == TestUtils.deployFactory(), "UniversalFactory not deployed"); - SigningKey memory signer = TestUtils.createSigner(admin.privateKey); - TssKey[] memory keys = new TssKey[](1); - keys[0] = TssKey({yParity: SigningUtils.yParity(signer) == 28 ? 3 : 2, xCoord: SigningUtils.xCoord(signer)}); // Shard key - Network[] memory networks = new Network[](2); - address proxyAddr = computeGatewayProxyAddress(admin.addr, salt); - networks[0].id = srcRoute; // sepolia network id - networks[0].gateway = proxyAddr; // sepolia proxy address - networks[1].id = dstRoute; // shibuya network id - networks[1].gateway = proxyAddr; // shibuya proxy address - return setupGateway(admin, salt, dstRoute, keys, networks); - } -} - -library VerifyingUtils { - function addr(VerifyingKey memory pubkey) internal pure returns (address) { - uint256 hash; - assembly { - hash := keccak256(pubkey, 0x40) - } - return address(uint160(hash)); - } - - function yParity(VerifyingKey memory pubkey) internal pure returns (uint8) { - return uint8(pubkey.py % 2) + 27; - } - - function challenge(VerifyingKey memory pubkey, bytes32 hash, address r) internal pure returns (uint256) { - return uint256(keccak256(abi.encodePacked(r, yParity(pubkey), pubkey.px, uint256(hash)))); - } - - function verifyPrehash(VerifyingKey memory pubkey, bytes32 prehash, uint256 c, uint256 z) - internal - pure - returns (bool) - { - return Schnorr.verify(yParity(pubkey), pubkey.px, uint256(prehash), c, z); - } - - function verify(VerifyingKey memory pubkey, bytes memory message, uint256 c, uint256 z) - internal - pure - returns (bool) - { - return verifyPrehash(pubkey, keccak256(message), c, z); - } -} - -library SigningUtils { - function addr(SigningKey memory signer) internal pure returns (address) { - return VerifyingUtils.addr(signer.pubkey); - } - - function yParity(SigningKey memory signer) internal pure returns (uint8) { - return uint8(signer.pubkey.py % 2) + 27; - } - - function xCoord(SigningKey memory signer) internal pure returns (uint256) { - return signer.pubkey.px; - } - - function challenge(SigningKey memory signer, bytes32 hash, address r) internal pure returns (uint256) { - return uint256(keccak256(abi.encodePacked(r, yParity(signer), signer.pubkey.px, uint256(hash)))); - } - - function signPrehashed(SigningKey memory signer, bytes32 hash, uint256 nonce) - internal - pure - returns (uint256, uint256) - { - (uint256 rx, uint256 ry) = SECP256K1.publicKey(nonce); - address r = SECP256K1.point_hash(rx, ry); - uint256 c = challenge(signer, hash, r); - uint256 z = addmod(nonce, mulmod(c, signer.secret, Schnorr.Q), Schnorr.Q); - return (c, z); - } - - function sign(SigningKey memory signer, bytes memory message, uint256 nonce) - internal - pure - returns (uint256, uint256) - { - return signPrehashed(signer, keccak256(message), nonce); - } - - function verifyPrehash(SigningKey memory signer, bytes32 prehash, uint256 c, uint256 z) - internal - pure - returns (bool) - { - return Schnorr.verify(yParity(signer), signer.pubkey.px, uint256(prehash), c, z); - } - - function verify(SigningKey memory signer, bytes memory message, uint256 c, uint256 z) - internal - pure - returns (bool) - { - return verifyPrehash(signer, keccak256(message), c, z); - } -} diff --git a/analog-gmp/test/utils/BaseTest.sol b/analog-gmp/test/utils/BaseTest.sol deleted file mode 100644 index 1eeedbb6e..000000000 --- a/analog-gmp/test/utils/BaseTest.sol +++ /dev/null @@ -1,125 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (test/utils/BaseTest.sol) - -pragma solidity >=0.8.0; - -import {IUniversalFactory} from "../../lib/universal-factory/src/IUniversalFactory.sol"; -import {FactoryUtils} from "../../lib/universal-factory/src/FactoryUtils.sol"; -import {Interpreter} from "../../lib/evm-interpreter/src/Interpreter.sol"; -import {Test, console, Vm} from "forge-std/Test.sol"; -import {VmSafe} from "forge-std/Vm.sol"; - -abstract contract BaseTest is Test { - using FactoryUtils for IUniversalFactory; - - /** - * @dev Universal Factory used to deploy contracts at deterministic addresses. - * see: https://github.com/Analog-Labs/Universal-factory - */ - IUniversalFactory internal constant FACTORY = IUniversalFactory(0x0000000000001C4Bf962dF86e38F0c10c7972C6E); - - /** - * @dev EVM Interpreter, used to extract the `type(Gateway).runtimeCode` from the `type(Gateway).creationCode`. - * see: https://github.com/analog-Labs/evm-interpreter - */ - address internal constant EVM_INTERPRETER = 0x0000000000001e3F4F615cd5e20c681Cf7d85e8D; - - /** - * @dev CREATE2 Salt used to deploy the `findBytes` contracts at deterministic addresses. - */ - bytes32 private constant CREATE2_SALT = bytes32(uint256(1234)); - - /** - * @dev Address who must deploy the `UniversalFactory` contract, to guarantee the same address on all networks. - */ - address internal constant FACTORY_DEPLOYER = 0x908064dE91a32edaC91393FEc3308E6624b85941; - - /** - * @dev The `findBytes` contract bytecode, used to find byte sequences in a given bytecode. - */ - bytes private constant FIND_BYTES = - hex"602e80600a5f395ff3fe60403610602a573d35601f5b6001018035821881361102600b576020813614602a5790033d5260203df35b3d3dfd"; - - /** - * @dev CODEHASH from the `findBytes` contract bytecode, used to compute the final CREATE2 address.. - */ - bytes32 private constant FIND_BYTES_CODEHASH = keccak256(FIND_BYTES); - - /** - * @dev All byte32 sequences in the form `0x7E7E7E7E7E7E...` will be replaced by the `INLINE_BYTECODE`. - */ - bytes32 private constant INLINE_BYTECODE = 0x6000823f505a96949290959391f15a607b019091036800000000000000000052; - - constructor() { - // Initialize the Universal Factory - if (address(FACTORY).code.length == 0) { - bytes memory creationCode = vm.getCode("./lib/universal-factory/abi/UniversalFactory.json"); - vm.deal(FACTORY_DEPLOYER, 100 ether); - vm.prank(FACTORY_DEPLOYER, FACTORY_DEPLOYER); - address factory; - assembly { - factory := create(0, add(creationCode, 32), mload(creationCode)) - } - require(factory == address(FACTORY), "Factory address mismatch"); - } - - // Initialize the EVM Interpreter - if (EVM_INTERPRETER.code.length == 0) { - bytes memory creationCode = vm.getCode("Interpreter.sol"); - address interpreter; - assembly { - interpreter := create(0, add(creationCode, 32), mload(creationCode)) - } - assertTrue(interpreter != address(0), "interpreter creation failed"); - assertGt(interpreter.code.length, 0, "interpreter code length mismatch"); - vm.etch(EVM_INTERPRETER, interpreter.code); - } - - // Deploy the find bytes contract - address findBytes = FACTORY.computeCreate2Address(CREATE2_SALT, FIND_BYTES_CODEHASH); - - // Initialize using the EVM interpreter - if (findBytes.code.length == 0) { - assertEq(FACTORY.create2(CREATE2_SALT, FIND_BYTES), findBytes, "find bytes creation failed"); - assembly { - // Copy code to memory - codecopy(0, 0, codesize()) - - // Execute constructor using the EVM interpreter - let success := delegatecall(gas(), EVM_INTERPRETER, 0, add(codesize(), 0x20), 0, 0) - - // Copy result to memory - returndatacopy(0x20, 0, returndatasize()) - if iszero(success) { revert(0x20, returndatasize()) } - - // COPY TAG to memory - let tag := mul(0x7E, 0x0101010101010101010101010101010101010101010101010101010101010101) - mstore(0x00, tag) - - // Find the `0x7E7E7E...` tag in the bytecode - let size := returndatasize() - if iszero(staticcall(gas(), findBytes, 0x00, add(size, 0x20), 0x00, 0x20)) { revert(0, 0) } - - // Replace the `0x7E7E7E...` by the `INLINE_BYTECODE` - let offset := add(mload(0x00), 0x20) - mstore(add(offset, 1), 0x5B) - mstore(offset, INLINE_BYTECODE) - - // Replace remaining occurences of `0x7E7E7E...` by the `INLINE_BYTECODE` - - for { let end := add(size, 0x20) } lt(offset, end) {} { - let backup := mload(offset) - mstore(offset, tag) - success := staticcall(gas(), findBytes, offset, sub(add(size, 0x20), offset), 0x00, 0x20) - mstore(offset, backup) - if iszero(success) { break } - offset := add(mload(0x00), 0x20) - mstore(add(offset, 1), 0x5B) - mstore(offset, INLINE_BYTECODE) - } - - return(0x20, size) - } - } - } -} diff --git a/analog-gmp/test/utils/GasSpender.sol b/analog-gmp/test/utils/GasSpender.sol deleted file mode 100644 index 265c74a6c..000000000 --- a/analog-gmp/test/utils/GasSpender.sol +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (test/utils/GasSpender.sol) - -pragma solidity >=0.8.0; - -import {IGmpReceiver} from "../../src/interfaces/IGmpReceiver.sol"; - -contract GasSpender is IGmpReceiver { - // Following a contract implements the IGmpReceiver interface and wastes the exact amount of gas you send to it in the payload field. - // OFFSET OPCODE - // 0x00 0x5a GAS - // 0x01 0x6002 PUSH1 0x02 - // 0x03 0x01 ADD - // 0x04 0x80 DUP1 - // 0x05 0x3d RETURNDATASIZE - // 0x06 0x52 MSTORE - // 0x07 0x3d RETURNDATASIZE - // 0x08 0x6020 PUSH1 0x20 - // 0x0a 0x91 SWAP2 - // 0x0b 0x6084 PUSH1 0x84 - // 0x0d 0x35 CALLDATALOAD -- Load the payload offset from the calldata - // 0x0e 0x6024 PUSH1 0x24 - // 0x10 0x01 ADD - // 0x11 0x35 CALLDATALOAD -- Load the gasToWaste from the payload - // 0x12 0x14 EQ - // 0x13 0x6018 PUSH1 0x18 - // ,=<0x15 0x57 JUMPI - // | 0x16 0x5b JUMPDEST - // | 0x17 0xfd REVERT -- Reverts if the gas left is less than the gas to waste. - // |=>0x18 0x5b JUMPDEST -- Waste 22 gas each on each iteration - // | 0x19 0x6036 PUSH1 0x36 - // | 0x1b 0x5a GAS - // | 0x1c 0x11 GT - // | 0x1d 0x6018 PUSH1 0x18 - // `=<0x1f 0x57 JUMPI - // 0x20 0x5a GAS - // 0x21 0x6049 PUSH1 0x49 - // 0x23 0x03 SUB - // ,=<0x24 0x56 JUMP -- Jumps depending on how much gas is left - // |=>0x25 0x5b JUMPDEST - // |=>0x26 0x5b JUMPDEST - // |=>0x27 0x5b JUMPDEST - // |=>0x28 0x5b JUMPDEST - // |=>0x29 0x5b JUMPDEST - // |=>0x2a 0x5b JUMPDEST - // |=>0x2b 0x5b JUMPDEST - // |=>0x2c 0x5b JUMPDEST - // |=>0x2d 0x5b JUMPDEST - // |=>0x2e 0x5b JUMPDEST - // |=>0x2f 0x5b JUMPDEST - // |=>0x30 0x5b JUMPDEST - // |=>0x31 0x5b JUMPDEST - // |=>0x32 0x5b JUMPDEST - // |=>0x33 0x5b JUMPDEST - // |=>0x34 0x5b JUMPDEST - // |=>0x35 0x5b JUMPDEST - // |=>0x36 0x5b JUMPDEST - // |=>0x37 0x5b JUMPDEST - // |=>0x38 0x5b JUMPDEST - // |=>0x39 0x5b JUMPDEST - // `=>0x3a 0x5b JUMPDEST - // 0x3b 0xf3 RETURN - bytes private constant BYTECODE = - hex"5a600201803d523d60209160843560240135146018575bfd5b60365a116018575a604903565b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5bf3"; - - constructor() payable { - bytes memory bytecode = BYTECODE; - assembly { - return(add(bytecode, 0x20), mload(bytecode)) - } - } - - function onGmpReceived(bytes32, uint128, bytes32, uint64, bytes calldata payload) external payable returns (bytes32) { - unchecked { - // OBS: This is just an example on how this contract works, the actual code is implemented directly in - // low level EVM, as defined in the `BYTECODE` constant. - uint256 initialGas = gasleft() + 2; - uint256 gasToWaste = abi.decode(payload, (uint256)); - require(initialGas > gasToWaste); - uint256 finalGas = initialGas - gasToWaste; - while (gasleft() > finalGas) {} - return bytes32(initialGas); - } - } -} diff --git a/analog-gmp/test/utils/GasSpender.t.sol b/analog-gmp/test/utils/GasSpender.t.sol deleted file mode 100644 index a838caf2e..000000000 --- a/analog-gmp/test/utils/GasSpender.t.sol +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (test/Gateway.t.sol) - -pragma solidity >=0.8.0; - -import {Test, console, Vm} from "forge-std/Test.sol"; -import {VmSafe} from "forge-std/Vm.sol"; -import {TestUtils} from "../TestUtils.sol"; -import {BaseTest} from "./BaseTest.sol"; -import {GasSpender} from "./GasSpender.sol"; -import {GasUtils} from "../../src/utils/GasUtils.sol"; -import {IGmpReceiver} from "../../src/interfaces/IGmpReceiver.sol"; - -contract GasSpenderTest is BaseTest { - function buildCall(uint256 gasToWaste) private pure returns (uint256 gasLimit, bytes memory encodedCall) { - // Encode the `IGmpReceiver.onGmpReceived` call - encodedCall = abi.encodeCall( - IGmpReceiver.onGmpReceived, - ( - 0x0000000000000000000000000000000000000000000000000000000000000000, - 1, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0, - abi.encode(gasToWaste) - ) - ); - gasLimit = TestUtils.calculateBaseCost(encodedCall) + gasToWaste; - } - - function test_onGmpReceivedWorks(uint16 delta) external { - // Guarantee the gas limit is not less than 1000 - uint256 gasToWaste = 1000 + uint256(delta); - vm.txGasPrice(1); - - // Create the Sender account - address sender = TestUtils.createTestAccount(100 ether); - - // Deploy the GasSpender contract - GasSpender spender = new GasSpender(); - - // Encode the `IGmpReceiver.onGmpReceived` call - (uint256 gasLimit, bytes memory encodedCall) = buildCall(gasToWaste); - - (uint256 gasUsed,, bytes memory output) = - TestUtils.executeCall(sender, address(spender), gasLimit, 0, encodedCall); - assertEq(gasUsed, gasToWaste); - assertEq(output.length, 32); - } - - function test_revertsMoreGas(uint16 delta) external { - // Guarantee the gas limit is not less than 1000 - uint256 gasToWaste = 1000 + uint256(delta); - vm.txGasPrice(1); - - // Create the Sender account - address sender = TestUtils.createTestAccount(100 ether); - - // Deploy the GasSpender contract - GasSpender spender = new GasSpender(); - - // Encode the `IGmpReceiver.onGmpReceived` call - (uint256 gasLimit, bytes memory encodedCall) = buildCall(gasToWaste); - - vm.expectRevert(); - TestUtils.executeCall(sender, address(spender), gasLimit + 1, 0, encodedCall); - } - - function test_revertsLessGas(uint16 delta) external { - // Guarantee the gas limit is not less than 1000 - uint256 gasToWaste = 1000 + uint256(delta); - vm.txGasPrice(1); - - // Create the Sender account - address sender = TestUtils.createTestAccount(100 ether); - - // Deploy the GasSpender contract - GasSpender spender = new GasSpender(); - - // Encode the `IGmpReceiver.onGmpReceived` call - (uint256 gasLimit, bytes memory encodedCall) = buildCall(gasToWaste); - - vm.expectRevert(); - TestUtils.executeCall(sender, address(spender), gasLimit - 1, 0, encodedCall); - } -} diff --git a/analog-gmp/test/utils/GmpProxy.sol b/analog-gmp/test/utils/GmpProxy.sol deleted file mode 100644 index 3fe0ebe34..000000000 --- a/analog-gmp/test/utils/GmpProxy.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MIT -// Analog's Contracts (last updated v0.1.0) (test/utils/GmpProxy.sol) - -pragma solidity >=0.8.0; - -import {ERC1967} from "../../src/utils/ERC1967.sol"; -import {IGmpReceiver} from "../../src/interfaces/IGmpReceiver.sol"; -import {IGateway} from "../../src/interfaces/IGateway.sol"; -import {BranchlessMath} from "../../src/utils/BranchlessMath.sol"; -import {console} from "forge-std/console.sol"; - -contract GmpProxy is IGmpReceiver { - using BranchlessMath for uint256; - - event MessageReceived(GmpMessage msg); - - struct GmpMessage { - bytes32 source; - uint16 srcNetwork; - address dest; - uint16 destNetwork; - uint64 gasLimit; - uint64 nonce; - bytes data; - } - - IGateway public immutable GATEWAY; - uint16 public immutable NETWORK_ID; - - constructor(address gateway) payable { - GATEWAY = IGateway(gateway); - NETWORK_ID = GATEWAY.networkId(); - } - - function sendMessage(GmpMessage calldata message) external payable returns (bytes32) { - uint256 value = address(this).balance.min(msg.value); - return GATEWAY.submitMessage{value: value}(message.dest, message.destNetwork, message.gasLimit, message.data); - } - - function onGmpReceived(bytes32 id, uint128 srcNetwork, bytes32 src, uint64 nonce, bytes calldata payload) external payable returns (bytes32) { - // when estimating gas an insane amount of gas is provided - uint256 gasLimit = gasleft(); - // this is the constant added to gasLimit - unchecked { console.log(300_000 - gasLimit); } - uint64 msgGasLimit; - unchecked { msgGasLimit = uint64(gasLimit + 579); } - GmpMessage memory message = GmpMessage({ - source: src, - srcNetwork: uint16(srcNetwork), - dest: address(this), - destNetwork: NETWORK_ID, - gasLimit: msgGasLimit, - nonce: nonce, - data: payload - }); - emit MessageReceived(message); - return id; - } -} diff --git a/analog-gmp/test/utils/GmpProxy.t.sol b/analog-gmp/test/utils/GmpProxy.t.sol deleted file mode 100644 index 813163c73..000000000 --- a/analog-gmp/test/utils/GmpProxy.t.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity >=0.8.0; - -import {Test, console, Vm} from "forge-std/Test.sol"; -import {IGateway} from "../../src/interfaces/IGateway.sol"; -import {IGmpReceiver} from "../../src/interfaces/IGmpReceiver.sol"; -import {GmpProxy} from "./GmpProxy.sol"; - -contract MockGateway is IGateway { - uint16 _networkId; - - constructor(uint16 network) { - _networkId = network; - } - - function networkId() external view returns (uint16) { - return _networkId; - } - - function estimateMessageCost( - uint16, - uint256, - uint256 - ) external pure returns (uint256) { - return 0; - } - - function submitMessage( - address, - uint16, - uint256, - bytes calldata - ) external payable returns (bytes32) { - return 0x0; - } -} - -contract GmpProxyTest is Test { - MockGateway gateway; - GmpProxy proxy; - - function setUp() external { - gateway = new MockGateway(1); - proxy = new GmpProxy(address(gateway)); - } - - function test_onGmpReceived() external { - proxy.onGmpReceived{gas: 300000}( - 0x0, - 0, - 0x0, - 0, - abi.encode(0x0) - ); - } -}