From eee08536dd3a56217522435cb5b3545abc615a4e Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 21 Jun 2024 14:56:47 +0200 Subject: [PATCH 01/51] cross-chain prototype v1 --- contracts/crosschain/GenericGatewayAxelar.sol | 198 ++++++++ contracts/crosschain/IGenericGateway.sol | 56 +++ .../axelar/interfaces/IAxelarGasService.sol | 436 ++++++++++++++++++ .../axelar/interfaces/IAxelarGateway.sol | 192 ++++++++ .../axelar/interfaces/IContractIdentifier.sol | 13 + .../vendor/axelar/interfaces/IGovernable.sol | 41 ++ .../axelar/interfaces/IImplementation.sol | 11 + .../interfaces/IInterchainGasEstimation.sol | 45 ++ .../vendor/axelar/interfaces/IOwnable.sol | 50 ++ .../vendor/axelar/interfaces/IUpgradable.sol | 23 + .../axelar/types/GasEstimationTypes.sol | 35 ++ contracts/utils/Set.sol | 32 ++ 12 files changed, 1132 insertions(+) create mode 100644 contracts/crosschain/GenericGatewayAxelar.sol create mode 100644 contracts/crosschain/IGenericGateway.sol create mode 100644 contracts/crosschain/vendor/axelar/interfaces/IAxelarGasService.sol create mode 100644 contracts/crosschain/vendor/axelar/interfaces/IAxelarGateway.sol create mode 100644 contracts/crosschain/vendor/axelar/interfaces/IContractIdentifier.sol create mode 100644 contracts/crosschain/vendor/axelar/interfaces/IGovernable.sol create mode 100644 contracts/crosschain/vendor/axelar/interfaces/IImplementation.sol create mode 100644 contracts/crosschain/vendor/axelar/interfaces/IInterchainGasEstimation.sol create mode 100644 contracts/crosschain/vendor/axelar/interfaces/IOwnable.sol create mode 100644 contracts/crosschain/vendor/axelar/interfaces/IUpgradable.sol create mode 100644 contracts/crosschain/vendor/axelar/types/GasEstimationTypes.sol create mode 100644 contracts/utils/Set.sol diff --git a/contracts/crosschain/GenericGatewayAxelar.sol b/contracts/crosschain/GenericGatewayAxelar.sol new file mode 100644 index 00000000..4de8c0dd --- /dev/null +++ b/contracts/crosschain/GenericGatewayAxelar.sol @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {IGenericGateway} from "./IGenericGateway.sol"; +import {Set} from "../utils/Set.sol"; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +import {Math} from "@openzeppelin/contracts@master/utils/math/Math.sol"; + +import {IAxelarGateway} from "./vendor/axelar/interfaces/IAxelarGateway.sol"; +import {IAxelarGasService} from "./vendor/axelar/interfaces/IAxelarGasService.sol"; + +contract GenericGatewayAxelar is IGenericGateway, Ownable { + using Set for Set.Bytes32Set; + + event RequestCreated(bytes32 id, Request req); + event RequestForwarded(bytes32 id); + event RequestExecuted(bytes32 id); + + IAxelarGateway public immutable gateway; + IAxelarGasService public immutable gasService; + Set.Bytes32Set private _outBox; + + struct ChainDetails { string name; string remote; } + mapping(uint256 chainId => ChainDetails chainName) public chainDetails; + + constructor(IAxelarGateway _gateway, address _initialOwner) Ownable(_initialOwner) { + gateway = _gateway; + } + + function registerForeignChainDetails(uint256 chainId, ChainDetails memory details) public onlyOwner() { + require(chainId != block.chainid); + require(bytes(chainDetails[chainId].name).length == 0); + chainDetails[chainId] = details; + } + + // =============================================== cost estimation =============================================== + + function defaultCost(Message memory /*message*/) public pure returns (address, uint256) { + return (address(0), 0); + } + + function estimateCost(Message memory /*message*/, address asset) public pure returns (uint256) { + return Math.ternary(asset == address(0), 0, type(uint256).max); + } + + // ================================================= 1 step mode ================================================= + + function sendRequest(uint256 chain, address target, bytes memory data, bytes32 salt) public payable returns (bytes32) { + Request memory req = _generateRequest(chain, target, msg.sender, 0, data, salt); + (address feeAsset, uint256 feeValue) = defaultCost(req.message); + return _sendRequest(req, feeAsset, feeValue); + } + + function sendRequest(uint256 chain, address target, bytes memory data, bytes32 salt, address feeAsset, uint256 feeValue) public payable returns (bytes32) { + Request memory req = _generateRequest(chain, target, msg.sender, 0, data, salt); + return _sendRequest(req, feeAsset, feeValue); + } + + function _sendRequest(Request memory req, address feeAsset, uint256 feeValue) internal returns (bytes32) { + // retrieve chain details for the destination chain + ChainDetails storage details = chainDetails[req.message.destination.chain]; + require(bytes(details.name).length > 0, "Remote chain not registered"); + + // rebuild request hash + bytes memory payload = abi.encode(req); + bytes32 id = keccak256(payload); + + // If value is provided, forward it to the gasService + require(feeAsset == address(0) && feeValue == msg.value); // Axelar only support ether + if (msg.value > 0) { + gasService.payNativeGasForContractCall{ value: msg.value }(address(this), details.name, details.remote, payload, msg.sender); + } + + // send cross-chain signal + gateway.callContract(details.name, details.remote, payload); + + // TODO: event + + return id; + } + + // ================================================= 2 step mode ================================================= + + function createRequest(uint256 chain, address target, bytes memory data, bytes32 salt) public payable returns (bytes32) { + require(msg.value == 0); // Axelar doesn't support value + + Request memory req = _generateRequest(chain, target, msg.sender, 0, data, salt); + return _createRequest(req); + } + + function _createRequest(Request memory req) internal returns (bytes32) { + // retrieve chain details for the destination chain + ChainDetails storage details = chainDetails[req.message.destination.chain]; + require(bytes(details.name).length > 0, "Remote chain not registered"); + + // compute the request hash + bytes memory payload = abi.encode(req); + bytes32 id = keccak256(payload); + + // register the request hash + require(_outBox.insert(id), "Ticket already scheduled"); + + // emit notice + emit RequestCreated(id, req); + + return id; + + } + + function forwardRequest(Request memory req) public payable { + (address feeAsset, uint256 feeValue) = defaultCost(req.message); + _forwardRequest(req, feeAsset, feeValue); + } + + function forwardRequest(Request memory req, address feeAsset, uint256 feeValue) public payable { + _forwardRequest(req, feeAsset, feeValue); + } + + function _forwardRequest(Request memory req, address feeAsset, uint256 feeValue) internal { + ChainDetails storage details = chainDetails[req.message.destination.chain]; + // Not needed, was verified during request creation + // require(bytes(details.name).length > 0, "Remote chain not registered"); + + // compute the request hash + bytes memory payload = abi.encode(req); + bytes32 id = keccak256(payload); + + // consume request hash + require(_outBox.remove(id), "Ticket not scheduled"); + + // If value is provided, forward it to the gasService + require(feeAsset == address(0) && feeValue == msg.value); + if (msg.value > 0) { + gasService.payNativeGasForContractCall{ value: msg.value }(address(this), details.name, details.remote, payload, msg.sender); + } + + // send cross-chain signal + gateway.callContract(details.name, details.remote, payload); + + // emit notice + emit RequestForwarded(id); + } + + // =========================================== receive end (specific) ============================================ + function executeRequest(Request memory req, bytes32 commandId) public payable { + // compute the request hash + bytes memory payload = abi.encode(req); + bytes32 id = keccak256(payload); + + // retrieve chain details for the source chain + ChainDetails storage details = chainDetails[req.source.chain]; + require(bytes(details.name).length > 0, "Remote chain not registered"); + + // validate operation (includes replay protection) + require(gateway.validateContractCall(commandId, details.name, details.remote, id)); + + // perform call + _executeRequest(req.message); + + // emit notice + emit RequestExecuted(id); + } + + // =================================================== helpers =================================================== + function _generateRequest( + uint256 chain, + address target, + address sender, + uint256 value, + bytes memory data, + bytes32 salt + ) internal view returns (Request memory) { + return Request({ + source: Account({ + chain: block.chainid, + instance: sender + }), + message: Message({ + destination: Account({ + chain: chain, + instance: target + }), + value: value, + data: data + }), + salt: salt + }); + } + + function _executeRequest(Message memory message) internal { + require(message.destination.chain == block.chainid); + (bool success, bytes memory returndata) = message.destination.instance.call{value: message.value}(message.data); + Address.verifyCallResult(success, returndata); + } +} diff --git a/contracts/crosschain/IGenericGateway.sol b/contracts/crosschain/IGenericGateway.sol new file mode 100644 index 00000000..471919a1 --- /dev/null +++ b/contracts/crosschain/IGenericGateway.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IGenericGateway { + struct Account { + uint256 chain; + address instance; + } + + // TODO: (address,uint256)[] tokens? + // TODO: put value in tokens? + struct Message { + Account destination; + uint256 value; + bytes data; + } + + struct Request { + Account source; + Message message; + bytes32 salt; + } + + // =============================================== cost estimation =============================================== + + function defaultCost(Message memory message) external view returns (address, uint256); + + /// @dev Returns the (minimum) cost (in a given asset) of performing a cross-chain operation. If asset is not supported for payment, returns type(uint256).max + function estimateCost(Message memory message, address asset) external view returns (uint256); + + // ================================================= 1 step mode ================================================= + + /// @dev Perform a cross-chain call using the canonical payment method for this bridge. The provided value is + /// passed along the request, minus anything that would be part of the canonical payment method. + function sendRequest(uint256 chain, address target, bytes memory data, bytes32 salt) external payable returns (bytes32); + + /// @dev Perform a cross-chain call using the specified payment method. If feeAsset is 0, then feeValue will be + /// deduced from the provided value to cover costs. The rest of the value is passed along the request. + function sendRequest(uint256 chain, address target, bytes memory data, bytes32 salt, address feeAsset, uint256 feeValue) external payable returns (bytes32); + + // ================================================= 2 step mode ================================================= + + /// @dev Register a cross-chain call that will later be forwarded using {forwardRequest}. Any value passed here + /// will be escrowed. It will then be passed along the request be forwarding happens. + function createRequest(uint256 chain, address target, bytes memory data, bytes32 salt) external payable returns (bytes32); + + /// @dev Forwards a cross-chain request using the canonical payment method for this bridge. Any value provided + /// here will be used for the payment. It will not be forwarded with the cross-chain call. + function forwardRequest(Request memory req) external payable; + + /// @dev Forwards a cross-chain request using using the specified payment method. Any value provided here will be + /// used for the payment. It will not be forwarded with the cross-chain call. This means that value should only be + /// used with `feeAsset = address(0)` and with `feeValue = msg.value`. + function forwardRequest(Request memory req, address feeAsset, uint256 feeValue) external payable; +} diff --git a/contracts/crosschain/vendor/axelar/interfaces/IAxelarGasService.sol b/contracts/crosschain/vendor/axelar/interfaces/IAxelarGasService.sol new file mode 100644 index 00000000..78ea0f1d --- /dev/null +++ b/contracts/crosschain/vendor/axelar/interfaces/IAxelarGasService.sol @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { GasInfo } from '../types/GasEstimationTypes.sol'; +import { IInterchainGasEstimation } from './IInterchainGasEstimation.sol'; +import { IUpgradable } from './IUpgradable.sol'; + +/** + * @title IAxelarGasService Interface + * @notice This is an interface for the AxelarGasService contract which manages gas payments + * and refunds for cross-chain communication on the Axelar network. + * @dev This interface inherits IUpgradable + */ +interface IAxelarGasService is IInterchainGasEstimation, IUpgradable { + error InvalidAddress(); + error NotCollector(); + error InvalidAmounts(); + error InvalidGasUpdates(); + error InvalidParams(); + error InsufficientGasPayment(uint256 required, uint256 provided); + + event GasPaidForContractCall( + address indexed sourceAddress, + string destinationChain, + string destinationAddress, + bytes32 indexed payloadHash, + address gasToken, + uint256 gasFeeAmount, + address refundAddress + ); + + event GasPaidForContractCallWithToken( + address indexed sourceAddress, + string destinationChain, + string destinationAddress, + bytes32 indexed payloadHash, + string symbol, + uint256 amount, + address gasToken, + uint256 gasFeeAmount, + address refundAddress + ); + + event NativeGasPaidForContractCall( + address indexed sourceAddress, + string destinationChain, + string destinationAddress, + bytes32 indexed payloadHash, + uint256 gasFeeAmount, + address refundAddress + ); + + event NativeGasPaidForContractCallWithToken( + address indexed sourceAddress, + string destinationChain, + string destinationAddress, + bytes32 indexed payloadHash, + string symbol, + uint256 amount, + uint256 gasFeeAmount, + address refundAddress + ); + + event GasPaidForExpressCall( + address indexed sourceAddress, + string destinationChain, + string destinationAddress, + bytes32 indexed payloadHash, + address gasToken, + uint256 gasFeeAmount, + address refundAddress + ); + + event GasPaidForExpressCallWithToken( + address indexed sourceAddress, + string destinationChain, + string destinationAddress, + bytes32 indexed payloadHash, + string symbol, + uint256 amount, + address gasToken, + uint256 gasFeeAmount, + address refundAddress + ); + + event NativeGasPaidForExpressCall( + address indexed sourceAddress, + string destinationChain, + string destinationAddress, + bytes32 indexed payloadHash, + uint256 gasFeeAmount, + address refundAddress + ); + + event NativeGasPaidForExpressCallWithToken( + address indexed sourceAddress, + string destinationChain, + string destinationAddress, + bytes32 indexed payloadHash, + string symbol, + uint256 amount, + uint256 gasFeeAmount, + address refundAddress + ); + + event GasAdded( + bytes32 indexed txHash, + uint256 indexed logIndex, + address gasToken, + uint256 gasFeeAmount, + address refundAddress + ); + + event NativeGasAdded(bytes32 indexed txHash, uint256 indexed logIndex, uint256 gasFeeAmount, address refundAddress); + + event ExpressGasAdded( + bytes32 indexed txHash, + uint256 indexed logIndex, + address gasToken, + uint256 gasFeeAmount, + address refundAddress + ); + + event NativeExpressGasAdded( + bytes32 indexed txHash, + uint256 indexed logIndex, + uint256 gasFeeAmount, + address refundAddress + ); + + event Refunded( + bytes32 indexed txHash, + uint256 indexed logIndex, + address payable receiver, + address token, + uint256 amount + ); + + /** + * @notice Pay for gas for any type of contract execution on a destination chain. + * @dev This function is called on the source chain before calling the gateway to execute a remote contract. + * @dev If estimateOnChain is true, the function will estimate the gas cost and revert if the payment is insufficient. + * @param sender The address making the payment + * @param destinationChain The target chain where the contract call will be made + * @param destinationAddress The target address on the destination chain + * @param payload Data payload for the contract call + * @param executionGasLimit The gas limit for the contract call + * @param estimateOnChain Flag to enable on-chain gas estimation + * @param refundAddress The address where refunds, if any, should be sent + * @param params Additional parameters for gas payment. This can be left empty for normal contract call payments. + */ + function payGas( + address sender, + string calldata destinationChain, + string calldata destinationAddress, + bytes calldata payload, + uint256 executionGasLimit, + bool estimateOnChain, + address refundAddress, + bytes calldata params + ) external payable; + + /** + * @notice Pay for gas using ERC20 tokens for a contract call on a destination chain. + * @dev This function is called on the source chain before calling the gateway to execute a remote contract. + * @param sender The address making the payment + * @param destinationChain The target chain where the contract call will be made + * @param destinationAddress The target address on the destination chain + * @param payload Data payload for the contract call + * @param gasToken The address of the ERC20 token used to pay for gas + * @param gasFeeAmount The amount of tokens to pay for gas + * @param refundAddress The address where refunds, if any, should be sent + */ + function payGasForContractCall( + address sender, + string calldata destinationChain, + string calldata destinationAddress, + bytes calldata payload, + address gasToken, + uint256 gasFeeAmount, + address refundAddress + ) external; + + /** + * @notice Pay for gas using ERC20 tokens for a contract call with tokens on a destination chain. + * @dev This function is called on the source chain before calling the gateway to execute a remote contract. + * @param sender The address making the payment + * @param destinationChain The target chain where the contract call with tokens will be made + * @param destinationAddress The target address on the destination chain + * @param payload Data payload for the contract call with tokens + * @param symbol The symbol of the token to be sent with the call + * @param amount The amount of tokens to be sent with the call + * @param gasToken The address of the ERC20 token used to pay for gas + * @param gasFeeAmount The amount of tokens to pay for gas + * @param refundAddress The address where refunds, if any, should be sent + */ + function payGasForContractCallWithToken( + address sender, + string calldata destinationChain, + string calldata destinationAddress, + bytes calldata payload, + string calldata symbol, + uint256 amount, + address gasToken, + uint256 gasFeeAmount, + address refundAddress + ) external; + + /** + * @notice Pay for gas using native currency for a contract call on a destination chain. + * @dev This function is called on the source chain before calling the gateway to execute a remote contract. + * @param sender The address making the payment + * @param destinationChain The target chain where the contract call will be made + * @param destinationAddress The target address on the destination chain + * @param payload Data payload for the contract call + * @param refundAddress The address where refunds, if any, should be sent + */ + function payNativeGasForContractCall( + address sender, + string calldata destinationChain, + string calldata destinationAddress, + bytes calldata payload, + address refundAddress + ) external payable; + + /** + * @notice Pay for gas using native currency for a contract call with tokens on a destination chain. + * @dev This function is called on the source chain before calling the gateway to execute a remote contract. + * @param sender The address making the payment + * @param destinationChain The target chain where the contract call with tokens will be made + * @param destinationAddress The target address on the destination chain + * @param payload Data payload for the contract call with tokens + * @param symbol The symbol of the token to be sent with the call + * @param amount The amount of tokens to be sent with the call + * @param refundAddress The address where refunds, if any, should be sent + */ + function payNativeGasForContractCallWithToken( + address sender, + string calldata destinationChain, + string calldata destinationAddress, + bytes calldata payload, + string calldata symbol, + uint256 amount, + address refundAddress + ) external payable; + + /** + * @notice Pay for gas using ERC20 tokens for an express contract call on a destination chain. + * @dev This function is called on the source chain before calling the gateway to express execute a remote contract. + * @param sender The address making the payment + * @param destinationChain The target chain where the contract call will be made + * @param destinationAddress The target address on the destination chain + * @param payload Data payload for the contract call + * @param gasToken The address of the ERC20 token used to pay for gas + * @param gasFeeAmount The amount of tokens to pay for gas + * @param refundAddress The address where refunds, if any, should be sent + */ + function payGasForExpressCall( + address sender, + string calldata destinationChain, + string calldata destinationAddress, + bytes calldata payload, + address gasToken, + uint256 gasFeeAmount, + address refundAddress + ) external; + + /** + * @notice Pay for gas using ERC20 tokens for an express contract call with tokens on a destination chain. + * @dev This function is called on the source chain before calling the gateway to express execute a remote contract. + * @param sender The address making the payment + * @param destinationChain The target chain where the contract call with tokens will be made + * @param destinationAddress The target address on the destination chain + * @param payload Data payload for the contract call with tokens + * @param symbol The symbol of the token to be sent with the call + * @param amount The amount of tokens to be sent with the call + * @param gasToken The address of the ERC20 token used to pay for gas + * @param gasFeeAmount The amount of tokens to pay for gas + * @param refundAddress The address where refunds, if any, should be sent + */ + function payGasForExpressCallWithToken( + address sender, + string calldata destinationChain, + string calldata destinationAddress, + bytes calldata payload, + string calldata symbol, + uint256 amount, + address gasToken, + uint256 gasFeeAmount, + address refundAddress + ) external; + + /** + * @notice Pay for gas using native currency for an express contract call on a destination chain. + * @dev This function is called on the source chain before calling the gateway to execute a remote contract. + * @param sender The address making the payment + * @param destinationChain The target chain where the contract call will be made + * @param destinationAddress The target address on the destination chain + * @param payload Data payload for the contract call + * @param refundAddress The address where refunds, if any, should be sent + */ + function payNativeGasForExpressCall( + address sender, + string calldata destinationChain, + string calldata destinationAddress, + bytes calldata payload, + address refundAddress + ) external payable; + + /** + * @notice Pay for gas using native currency for an express contract call with tokens on a destination chain. + * @dev This function is called on the source chain before calling the gateway to execute a remote contract. + * @param sender The address making the payment + * @param destinationChain The target chain where the contract call with tokens will be made + * @param destinationAddress The target address on the destination chain + * @param payload Data payload for the contract call with tokens + * @param symbol The symbol of the token to be sent with the call + * @param amount The amount of tokens to be sent with the call + * @param refundAddress The address where refunds, if any, should be sent + */ + function payNativeGasForExpressCallWithToken( + address sender, + string calldata destinationChain, + string calldata destinationAddress, + bytes calldata payload, + string calldata symbol, + uint256 amount, + address refundAddress + ) external payable; + + /** + * @notice Add additional gas payment using ERC20 tokens after initiating a cross-chain call. + * @dev This function can be called on the source chain after calling the gateway to execute a remote contract. + * @param txHash The transaction hash of the cross-chain call + * @param logIndex The log index for the cross-chain call + * @param gasToken The ERC20 token address used to add gas + * @param gasFeeAmount The amount of tokens to add as gas + * @param refundAddress The address where refunds, if any, should be sent + */ + function addGas( + bytes32 txHash, + uint256 logIndex, + address gasToken, + uint256 gasFeeAmount, + address refundAddress + ) external; + + /** + * @notice Add additional gas payment using native currency after initiating a cross-chain call. + * @dev This function can be called on the source chain after calling the gateway to execute a remote contract. + * @param txHash The transaction hash of the cross-chain call + * @param logIndex The log index for the cross-chain call + * @param refundAddress The address where refunds, if any, should be sent + */ + function addNativeGas( + bytes32 txHash, + uint256 logIndex, + address refundAddress + ) external payable; + + /** + * @notice Add additional gas payment using ERC20 tokens after initiating an express cross-chain call. + * @dev This function can be called on the source chain after calling the gateway to express execute a remote contract. + * @param txHash The transaction hash of the cross-chain call + * @param logIndex The log index for the cross-chain call + * @param gasToken The ERC20 token address used to add gas + * @param gasFeeAmount The amount of tokens to add as gas + * @param refundAddress The address where refunds, if any, should be sent + */ + function addExpressGas( + bytes32 txHash, + uint256 logIndex, + address gasToken, + uint256 gasFeeAmount, + address refundAddress + ) external; + + /** + * @notice Add additional gas payment using native currency after initiating an express cross-chain call. + * @dev This function can be called on the source chain after calling the gateway to express execute a remote contract. + * @param txHash The transaction hash of the cross-chain call + * @param logIndex The log index for the cross-chain call + * @param refundAddress The address where refunds, if any, should be sent + */ + function addNativeExpressGas( + bytes32 txHash, + uint256 logIndex, + address refundAddress + ) external payable; + + /** + * @notice Updates the gas price for a specific chain. + * @dev This function is called by the gas oracle to update the gas prices for a specific chains. + * @param chains Array of chain names + * @param gasUpdates Array of gas updates + */ + function updateGasInfo(string[] calldata chains, GasInfo[] calldata gasUpdates) external; + + /** + * @notice Allows the gasCollector to collect accumulated fees from the contract. + * @dev Use address(0) as the token address for native currency. + * @param receiver The address to receive the collected fees + * @param tokens Array of token addresses to be collected + * @param amounts Array of amounts to be collected for each respective token address + */ + function collectFees( + address payable receiver, + address[] calldata tokens, + uint256[] calldata amounts + ) external; + + /** + * @notice Refunds gas payment to the receiver in relation to a specific cross-chain transaction. + * @dev Only callable by the gasCollector. + * @dev Use address(0) as the token address to refund native currency. + * @param txHash The transaction hash of the cross-chain call + * @param logIndex The log index for the cross-chain call + * @param receiver The address to receive the refund + * @param token The token address to be refunded + * @param amount The amount to refund + */ + function refund( + bytes32 txHash, + uint256 logIndex, + address payable receiver, + address token, + uint256 amount + ) external; + + /** + * @notice Returns the address of the designated gas collector. + * @return address of the gas collector + */ + function gasCollector() external returns (address); +} diff --git a/contracts/crosschain/vendor/axelar/interfaces/IAxelarGateway.sol b/contracts/crosschain/vendor/axelar/interfaces/IAxelarGateway.sol new file mode 100644 index 00000000..92f891cc --- /dev/null +++ b/contracts/crosschain/vendor/axelar/interfaces/IAxelarGateway.sol @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { IGovernable } from './IGovernable.sol'; +import { IImplementation } from './IImplementation.sol'; + +interface IAxelarGateway is IImplementation, IGovernable { + /**********\ + |* Errors *| + \**********/ + + error NotSelf(); + error InvalidCodeHash(); + error SetupFailed(); + error InvalidAuthModule(); + error InvalidTokenDeployer(); + error InvalidAmount(); + error InvalidChainId(); + error InvalidCommands(); + error TokenDoesNotExist(string symbol); + error TokenAlreadyExists(string symbol); + error TokenDeployFailed(string symbol); + error TokenContractDoesNotExist(address token); + error BurnFailed(string symbol); + error MintFailed(string symbol); + error InvalidSetMintLimitsParams(); + error ExceedMintLimit(string symbol); + + /**********\ + |* Events *| + \**********/ + + event TokenSent( + address indexed sender, + string destinationChain, + string destinationAddress, + string symbol, + uint256 amount + ); + + event ContractCall( + address indexed sender, + string destinationChain, + string destinationContractAddress, + bytes32 indexed payloadHash, + bytes payload + ); + + event ContractCallWithToken( + address indexed sender, + string destinationChain, + string destinationContractAddress, + bytes32 indexed payloadHash, + bytes payload, + string symbol, + uint256 amount + ); + + event Executed(bytes32 indexed commandId); + + event TokenDeployed(string symbol, address tokenAddresses); + + event ContractCallApproved( + bytes32 indexed commandId, + string sourceChain, + string sourceAddress, + address indexed contractAddress, + bytes32 indexed payloadHash, + bytes32 sourceTxHash, + uint256 sourceEventIndex + ); + + event ContractCallApprovedWithMint( + bytes32 indexed commandId, + string sourceChain, + string sourceAddress, + address indexed contractAddress, + bytes32 indexed payloadHash, + string symbol, + uint256 amount, + bytes32 sourceTxHash, + uint256 sourceEventIndex + ); + + event ContractCallExecuted(bytes32 indexed commandId); + + event TokenMintLimitUpdated(string symbol, uint256 limit); + + event OperatorshipTransferred(bytes newOperatorsData); + + event Upgraded(address indexed implementation); + + /********************\ + |* Public Functions *| + \********************/ + + function sendToken( + string calldata destinationChain, + string calldata destinationAddress, + string calldata symbol, + uint256 amount + ) external; + + function callContract( + string calldata destinationChain, + string calldata contractAddress, + bytes calldata payload + ) external; + + function callContractWithToken( + string calldata destinationChain, + string calldata contractAddress, + bytes calldata payload, + string calldata symbol, + uint256 amount + ) external; + + function isContractCallApproved( + bytes32 commandId, + string calldata sourceChain, + string calldata sourceAddress, + address contractAddress, + bytes32 payloadHash + ) external view returns (bool); + + function isContractCallAndMintApproved( + bytes32 commandId, + string calldata sourceChain, + string calldata sourceAddress, + address contractAddress, + bytes32 payloadHash, + string calldata symbol, + uint256 amount + ) external view returns (bool); + + function validateContractCall( + bytes32 commandId, + string calldata sourceChain, + string calldata sourceAddress, + bytes32 payloadHash + ) external returns (bool); + + function validateContractCallAndMint( + bytes32 commandId, + string calldata sourceChain, + string calldata sourceAddress, + bytes32 payloadHash, + string calldata symbol, + uint256 amount + ) external returns (bool); + + /***********\ + |* Getters *| + \***********/ + + function authModule() external view returns (address); + + function tokenDeployer() external view returns (address); + + function tokenMintLimit(string memory symbol) external view returns (uint256); + + function tokenMintAmount(string memory symbol) external view returns (uint256); + + function allTokensFrozen() external view returns (bool); + + function implementation() external view returns (address); + + function tokenAddresses(string memory symbol) external view returns (address); + + function tokenFrozen(string memory symbol) external view returns (bool); + + function isCommandExecuted(bytes32 commandId) external view returns (bool); + + /************************\ + |* Governance Functions *| + \************************/ + + function setTokenMintLimits(string[] calldata symbols, uint256[] calldata limits) external; + + function upgrade( + address newImplementation, + bytes32 newImplementationCodeHash, + bytes calldata setupParams + ) external; + + /**********************\ + |* External Functions *| + \**********************/ + + function execute(bytes calldata input) external; +} \ No newline at end of file diff --git a/contracts/crosschain/vendor/axelar/interfaces/IContractIdentifier.sol b/contracts/crosschain/vendor/axelar/interfaces/IContractIdentifier.sol new file mode 100644 index 00000000..bf32f96c --- /dev/null +++ b/contracts/crosschain/vendor/axelar/interfaces/IContractIdentifier.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +// General interface for upgradable contracts +interface IContractIdentifier { + /** + * @notice Returns the contract ID. It can be used as a check during upgrades. + * @dev Meant to be overridden in derived contracts. + * @return bytes32 The contract ID + */ + function contractId() external pure returns (bytes32); +} diff --git a/contracts/crosschain/vendor/axelar/interfaces/IGovernable.sol b/contracts/crosschain/vendor/axelar/interfaces/IGovernable.sol new file mode 100644 index 00000000..c08f4afc --- /dev/null +++ b/contracts/crosschain/vendor/axelar/interfaces/IGovernable.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/** + * @title IGovernable Interface + * @notice This is an interface used by the AxelarGateway contract to manage governance and mint limiter roles. + */ +interface IGovernable { + error NotGovernance(); + error NotMintLimiter(); + error InvalidGovernance(); + error InvalidMintLimiter(); + + event GovernanceTransferred(address indexed previousGovernance, address indexed newGovernance); + event MintLimiterTransferred(address indexed previousGovernance, address indexed newGovernance); + + /** + * @notice Returns the governance address. + * @return address of the governance + */ + function governance() external view returns (address); + + /** + * @notice Returns the mint limiter address. + * @return address of the mint limiter + */ + function mintLimiter() external view returns (address); + + /** + * @notice Transfer the governance role to another address. + * @param newGovernance The new governance address + */ + function transferGovernance(address newGovernance) external; + + /** + * @notice Transfer the mint limiter role to another address. + * @param newGovernance The new mint limiter address + */ + function transferMintLimiter(address newGovernance) external; +} diff --git a/contracts/crosschain/vendor/axelar/interfaces/IImplementation.sol b/contracts/crosschain/vendor/axelar/interfaces/IImplementation.sol new file mode 100644 index 00000000..037b3ce8 --- /dev/null +++ b/contracts/crosschain/vendor/axelar/interfaces/IImplementation.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { IContractIdentifier } from './IContractIdentifier.sol'; + +interface IImplementation is IContractIdentifier { + error NotProxy(); + + function setup(bytes calldata data) external; +} diff --git a/contracts/crosschain/vendor/axelar/interfaces/IInterchainGasEstimation.sol b/contracts/crosschain/vendor/axelar/interfaces/IInterchainGasEstimation.sol new file mode 100644 index 00000000..cab0ba6b --- /dev/null +++ b/contracts/crosschain/vendor/axelar/interfaces/IInterchainGasEstimation.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { GasEstimationType, GasInfo } from '../types/GasEstimationTypes.sol'; + +/** + * @title IInterchainGasEstimation Interface + * @notice This is an interface for the InterchainGasEstimation contract + * which allows for estimating gas fees for cross-chain communication on the Axelar network. + */ +interface IInterchainGasEstimation { + error UnsupportedEstimationType(GasEstimationType gasEstimationType); + + /** + * @notice Event emitted when the gas price for a specific chain is updated. + * @param chain The name of the chain + * @param info The gas info for the chain + */ + event GasInfoUpdated(string chain, GasInfo info); + + /** + * @notice Returns the gas price for a specific chain. + * @param chain The name of the chain + * @return gasInfo The gas info for the chain + */ + function getGasInfo(string calldata chain) external view returns (GasInfo memory); + + /** + * @notice Estimates the gas fee for a cross-chain contract call. + * @param destinationChain Axelar registered name of the destination chain + * @param destinationAddress Destination contract address being called + * @param executionGasLimit The gas limit to be used for the destination contract execution, + * e.g. pass in 200k if your app consumes needs upto 200k for this contract call + * @param params Additional parameters for the gas estimation + * @return gasEstimate The cross-chain gas estimate, in terms of source chain's native gas token that should be forwarded to the gas service. + */ + function estimateGasFee( + string calldata destinationChain, + string calldata destinationAddress, + bytes calldata payload, + uint256 executionGasLimit, + bytes calldata params + ) external view returns (uint256 gasEstimate); +} diff --git a/contracts/crosschain/vendor/axelar/interfaces/IOwnable.sol b/contracts/crosschain/vendor/axelar/interfaces/IOwnable.sol new file mode 100644 index 00000000..725bcb36 --- /dev/null +++ b/contracts/crosschain/vendor/axelar/interfaces/IOwnable.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/** + * @title IOwnable Interface + * @notice IOwnable is an interface that abstracts the implementation of a + * contract with ownership control features. It's commonly used in upgradable + * contracts and includes the functionality to get current owner, transfer + * ownership, and propose and accept ownership. + */ +interface IOwnable { + error NotOwner(); + error InvalidOwner(); + error InvalidOwnerAddress(); + + event OwnershipTransferStarted(address indexed newOwner); + event OwnershipTransferred(address indexed newOwner); + + /** + * @notice Returns the current owner of the contract. + * @return address The address of the current owner + */ + function owner() external view returns (address); + + /** + * @notice Returns the address of the pending owner of the contract. + * @return address The address of the pending owner + */ + function pendingOwner() external view returns (address); + + /** + * @notice Transfers ownership of the contract to a new address + * @param newOwner The address to transfer ownership to + */ + function transferOwnership(address newOwner) external; + + /** + * @notice Proposes to transfer the contract's ownership to a new address. + * The new owner needs to accept the ownership explicitly. + * @param newOwner The address to transfer ownership to + */ + function proposeOwnership(address newOwner) external; + + /** + * @notice Transfers ownership to the pending owner. + * @dev Can only be called by the pending owner + */ + function acceptOwnership() external; +} diff --git a/contracts/crosschain/vendor/axelar/interfaces/IUpgradable.sol b/contracts/crosschain/vendor/axelar/interfaces/IUpgradable.sol new file mode 100644 index 00000000..0ef082fb --- /dev/null +++ b/contracts/crosschain/vendor/axelar/interfaces/IUpgradable.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { IOwnable } from './IOwnable.sol'; +import { IImplementation } from './IImplementation.sol'; + +// General interface for upgradable contracts +interface IUpgradable is IOwnable, IImplementation { + error InvalidCodeHash(); + error InvalidImplementation(); + error SetupFailed(); + + event Upgraded(address indexed newImplementation); + + function implementation() external view returns (address); + + function upgrade( + address newImplementation, + bytes32 newImplementationCodeHash, + bytes calldata params + ) external; +} diff --git a/contracts/crosschain/vendor/axelar/types/GasEstimationTypes.sol b/contracts/crosschain/vendor/axelar/types/GasEstimationTypes.sol new file mode 100644 index 00000000..7a3aa14d --- /dev/null +++ b/contracts/crosschain/vendor/axelar/types/GasEstimationTypes.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/** + * @title GasEstimationType + * @notice This enum represents the gas estimation types for different chains. + */ +enum GasEstimationType { + Default, + OptimismEcotone, + OptimismBedrock, + Arbitrum, + Scroll +} + +/** + * @title GasInfo + * @notice This struct represents the gas pricing information for a specific chain. + * @dev Smaller uint types are used for efficient struct packing to save storage costs. + */ +struct GasInfo { + /// @dev Custom gas pricing rule, such as L1 data fee on L2s + uint64 gasEstimationType; + /// @dev Scalar value needed for specific gas estimation types, expected to be less than 1e10 + uint64 l1FeeScalar; + /// @dev Axelar base fee for cross-chain message approval on destination, in terms of source native gas token + uint128 axelarBaseFee; + /// @dev Gas price of destination chain, in terms of the source chain token, i.e dest_gas_price * dest_token_market_price / src_token_market_price + uint128 relativeGasPrice; + /// @dev Needed for specific gas estimation types. Blob base fee of destination chain, in terms of the source chain token, i.e dest_blob_base_fee * dest_token_market_price / src_token_market_price + uint128 relativeBlobBaseFee; + /// @dev Axelar express fee for express execution, in terms of source chain token + uint128 expressFee; +} diff --git a/contracts/utils/Set.sol b/contracts/utils/Set.sol new file mode 100644 index 00000000..5ad58f30 --- /dev/null +++ b/contracts/utils/Set.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +/// @dev non-iterable variant of OpenZeppelin's EnumerableSet library. +library Set { + struct Bytes32Set { + mapping(bytes32 value => bool) _data; + } + + function insert(Bytes32Set storage self, bytes32 value) internal returns (bool) { + if (!self._data[value]) { + self._data[value] = true; + return true; + } else { + return false; + } + } + + function remove(Bytes32Set storage self, bytes32 value) internal returns (bool) { + if (self._data[value]) { + self._data[value] = false; + return true; + } else { + return false; + } + } + + function contains(Bytes32Set storage self, bytes32 value) internal view returns (bool) { + return self._data[value]; + } +} \ No newline at end of file From 50ced4acdd465f7e952ad12e9b341a8dc50d5112 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 21 Jun 2024 15:45:42 +0200 Subject: [PATCH 02/51] split common <> axelar --- contracts/crosschain/GenericGatewayAxelar.sol | 187 ++++-------------- contracts/crosschain/GenericGatewayCommon.sol | 139 +++++++++++++ contracts/crosschain/IGenericGateway.sol | 25 ++- .../axelar/interfaces/IAxelarGasService.sol | 32 +-- .../axelar/interfaces/IAxelarGateway.sol | 12 +- .../axelar/interfaces/IImplementation.sol | 2 +- .../interfaces/IInterchainGasEstimation.sol | 2 +- .../vendor/axelar/interfaces/IUpgradable.sol | 10 +- contracts/utils/Set.sol | 2 +- 9 files changed, 215 insertions(+), 196 deletions(-) create mode 100644 contracts/crosschain/GenericGatewayCommon.sol diff --git a/contracts/crosschain/GenericGatewayAxelar.sol b/contracts/crosschain/GenericGatewayAxelar.sol index 4de8c0dd..ed7d7ac7 100644 --- a/contracts/crosschain/GenericGatewayAxelar.sol +++ b/contracts/crosschain/GenericGatewayAxelar.sol @@ -2,197 +2,84 @@ pragma solidity ^0.8.0; -import {IGenericGateway} from "./IGenericGateway.sol"; -import {Set} from "../utils/Set.sol"; - -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; -import {Math} from "@openzeppelin/contracts@master/utils/math/Math.sol"; - import {IAxelarGateway} from "./vendor/axelar/interfaces/IAxelarGateway.sol"; import {IAxelarGasService} from "./vendor/axelar/interfaces/IAxelarGasService.sol"; -contract GenericGatewayAxelar is IGenericGateway, Ownable { - using Set for Set.Bytes32Set; +import {GenericGatewayCommon} from "./GenericGatewayCommon.sol"; - event RequestCreated(bytes32 id, Request req); - event RequestForwarded(bytes32 id); - event RequestExecuted(bytes32 id); +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Math} from "@openzeppelin/contracts@master/utils/math/Math.sol"; +contract GenericGatewayAxelar is GenericGatewayCommon, Ownable { IAxelarGateway public immutable gateway; IAxelarGasService public immutable gasService; - Set.Bytes32Set private _outBox; - struct ChainDetails { string name; string remote; } + struct ChainDetails { + string name; + string remote; + } mapping(uint256 chainId => ChainDetails chainName) public chainDetails; constructor(IAxelarGateway _gateway, address _initialOwner) Ownable(_initialOwner) { gateway = _gateway; } - function registerForeignChainDetails(uint256 chainId, ChainDetails memory details) public onlyOwner() { + function registerForeignChainDetails(uint256 chainId, ChainDetails memory details) public onlyOwner { require(chainId != block.chainid); require(bytes(chainDetails[chainId].name).length == 0); chainDetails[chainId] = details; } - // =============================================== cost estimation =============================================== - - function defaultCost(Message memory /*message*/) public pure returns (address, uint256) { + function defaultCost(Message memory /*message*/) public pure virtual override returns (address, uint256) { return (address(0), 0); } - function estimateCost(Message memory /*message*/, address asset) public pure returns (uint256) { + function estimateCost(Message memory /*message*/, address asset) public pure virtual returns (uint256) { return Math.ternary(asset == address(0), 0, type(uint256).max); } - // ================================================= 1 step mode ================================================= - - function sendRequest(uint256 chain, address target, bytes memory data, bytes32 salt) public payable returns (bytes32) { - Request memory req = _generateRequest(chain, target, msg.sender, 0, data, salt); - (address feeAsset, uint256 feeValue) = defaultCost(req.message); - return _sendRequest(req, feeAsset, feeValue); - } - - function sendRequest(uint256 chain, address target, bytes memory data, bytes32 salt, address feeAsset, uint256 feeValue) public payable returns (bytes32) { - Request memory req = _generateRequest(chain, target, msg.sender, 0, data, salt); - return _sendRequest(req, feeAsset, feeValue); - } + /// @dev Override that the target blockchain is registered and that 0 value is passed when creating a request. + function createRequest( + uint256 chain, + address target, + bytes memory data, + bytes32 salt + ) public payable virtual override returns (bytes32) { + require(msg.value == 0, "Axelar does not support native currency bridging"); - function _sendRequest(Request memory req, address feeAsset, uint256 feeValue) internal returns (bytes32) { // retrieve chain details for the destination chain - ChainDetails storage details = chainDetails[req.message.destination.chain]; + ChainDetails storage details = chainDetails[chain]; require(bytes(details.name).length > 0, "Remote chain not registered"); - // rebuild request hash - bytes memory payload = abi.encode(req); - bytes32 id = keccak256(payload); - - // If value is provided, forward it to the gasService - require(feeAsset == address(0) && feeValue == msg.value); // Axelar only support ether - if (msg.value > 0) { - gasService.payNativeGasForContractCall{ value: msg.value }(address(this), details.name, details.remote, payload, msg.sender); - } - - // send cross-chain signal - gateway.callContract(details.name, details.remote, payload); - - // TODO: event - - return id; + return super.createRequest(chain, target, data, salt); } - // ================================================= 2 step mode ================================================= + function _processRequest( + bytes32 /*id*/, + Request memory req, + address feeAsset, + uint256 feeValue + ) internal virtual override { + require(feeAsset == address(0), "Axelar only supports fees in native currency"); + require(req.message.value == 0, "Axelar does not support native currency bridging"); - function createRequest(uint256 chain, address target, bytes memory data, bytes32 salt) public payable returns (bytes32) { - require(msg.value == 0); // Axelar doesn't support value - - Request memory req = _generateRequest(chain, target, msg.sender, 0, data, salt); - return _createRequest(req); - } - - function _createRequest(Request memory req) internal returns (bytes32) { - // retrieve chain details for the destination chain ChainDetails storage details = chainDetails[req.message.destination.chain]; require(bytes(details.name).length > 0, "Remote chain not registered"); - // compute the request hash bytes memory payload = abi.encode(req); - bytes32 id = keccak256(payload); - - // register the request hash - require(_outBox.insert(id), "Ticket already scheduled"); - - // emit notice - emit RequestCreated(id, req); - - return id; - - } - - function forwardRequest(Request memory req) public payable { - (address feeAsset, uint256 feeValue) = defaultCost(req.message); - _forwardRequest(req, feeAsset, feeValue); - } - - function forwardRequest(Request memory req, address feeAsset, uint256 feeValue) public payable { - _forwardRequest(req, feeAsset, feeValue); - } - - function _forwardRequest(Request memory req, address feeAsset, uint256 feeValue) internal { - ChainDetails storage details = chainDetails[req.message.destination.chain]; - // Not needed, was verified during request creation - // require(bytes(details.name).length > 0, "Remote chain not registered"); - - // compute the request hash - bytes memory payload = abi.encode(req); - bytes32 id = keccak256(payload); - - // consume request hash - require(_outBox.remove(id), "Ticket not scheduled"); // If value is provided, forward it to the gasService - require(feeAsset == address(0) && feeValue == msg.value); - if (msg.value > 0) { - gasService.payNativeGasForContractCall{ value: msg.value }(address(this), details.name, details.remote, payload, msg.sender); + if (feeValue > 0) { + gasService.payNativeGasForContractCall{value: feeValue}( + address(this), + details.name, + details.remote, + payload, + msg.sender + ); } // send cross-chain signal gateway.callContract(details.name, details.remote, payload); - - // emit notice - emit RequestForwarded(id); - } - - // =========================================== receive end (specific) ============================================ - function executeRequest(Request memory req, bytes32 commandId) public payable { - // compute the request hash - bytes memory payload = abi.encode(req); - bytes32 id = keccak256(payload); - - // retrieve chain details for the source chain - ChainDetails storage details = chainDetails[req.source.chain]; - require(bytes(details.name).length > 0, "Remote chain not registered"); - - // validate operation (includes replay protection) - require(gateway.validateContractCall(commandId, details.name, details.remote, id)); - - // perform call - _executeRequest(req.message); - - // emit notice - emit RequestExecuted(id); - } - - // =================================================== helpers =================================================== - function _generateRequest( - uint256 chain, - address target, - address sender, - uint256 value, - bytes memory data, - bytes32 salt - ) internal view returns (Request memory) { - return Request({ - source: Account({ - chain: block.chainid, - instance: sender - }), - message: Message({ - destination: Account({ - chain: chain, - instance: target - }), - value: value, - data: data - }), - salt: salt - }); - } - - function _executeRequest(Message memory message) internal { - require(message.destination.chain == block.chainid); - (bool success, bytes memory returndata) = message.destination.instance.call{value: message.value}(message.data); - Address.verifyCallResult(success, returndata); } } diff --git a/contracts/crosschain/GenericGatewayCommon.sol b/contracts/crosschain/GenericGatewayCommon.sol new file mode 100644 index 00000000..e1a5b312 --- /dev/null +++ b/contracts/crosschain/GenericGatewayCommon.sol @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {IGenericGateway} from "./IGenericGateway.sol"; +import {Set} from "../utils/Set.sol"; + +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +abstract contract GenericGatewayCommon is IGenericGateway { + using Set for Set.Bytes32Set; + + event RequestCreated(bytes32 id, Request req); + event RequestForwarded(bytes32 id); + event RequestExecuted(bytes32 id); + + Set.Bytes32Set private _outBox; + + // This must be redeclared as public so that other function can call it + function defaultCost(Message memory /*message*/) public pure virtual returns (address, uint256); + + function sendRequest( + uint256 chain, + address target, + uint256 value, + bytes memory data, + bytes32 salt + ) public payable virtual returns (bytes32) { + (address feeAsset, uint256 feeValue) = defaultCost( + Message({destination: Account({chain: chain, instance: target}), value: value, data: data}) + ); + return sendRequest(chain, target, value, data, salt, feeAsset, feeValue); + } + + function sendRequest( + uint256 chain, + address target, + uint256 value, + bytes memory data, + bytes32 salt, + address feeAsset, + uint256 feeValue + ) public payable virtual returns (bytes32) { + // build request, payload and hash + Request memory req = _generateRequest(chain, target, msg.sender, value, data, salt); + bytes32 id = keccak256(abi.encode(req)); + + if (feeAsset == address(0)) { + uint256 totalValue = value + feeValue; + require(msg.value >= totalValue, "invalid value provided"); + if (msg.value > totalValue) Address.sendValue(payable(msg.sender), msg.value - totalValue); + } else { + require(msg.value >= value, "invalid value provided"); + if (feeValue > 0) SafeERC20.safeTransferFrom(IERC20(feeAsset), msg.sender, address(this), feeValue); + if (msg.value > value) Address.sendValue(payable(msg.sender), msg.value - value); + } + + _processRequest(id, req, feeAsset, feeValue); + + // TODO: event + + return id; + } + + // ================================================= 2 step mode ================================================= + + function createRequest( + uint256 chain, + address target, + bytes memory data, + bytes32 salt + ) public payable virtual returns (bytes32) { + // build request, payload and hash + Request memory req = _generateRequest(chain, target, msg.sender, msg.value, data, salt); + bytes32 id = keccak256(abi.encode(req)); + + // register the request hash + require(_outBox.insert(id), "Ticket already scheduled"); + + // emit notice + emit RequestCreated(id, req); + + return id; + } + + function forwardRequest(Request memory req) public payable virtual { + (address feeAsset, uint256 feeValue) = defaultCost(req.message); + forwardRequest(req, feeAsset, feeValue); + } + + function forwardRequest(Request memory req, address feeAsset, uint256 feeValue) public payable virtual { + // compute the request hash + bytes32 id = keccak256(abi.encode(req)); + + if (feeAsset == address(0)) { + require(msg.value >= feeValue, "invalid value provided"); + if (msg.value > feeValue) Address.sendValue(payable(msg.sender), msg.value - feeValue); + } else { + if (feeValue > 0) SafeERC20.safeTransferFrom(IERC20(feeAsset), msg.sender, address(this), feeValue); + if (msg.value > 0) Address.sendValue(payable(msg.sender), msg.value); + } + + // consume request hash + require(_outBox.remove(id), "Ticket not scheduled"); + + _processRequest(id, req, feeAsset, feeValue); + + // emit notice + emit RequestForwarded(id); + } + + // =============================================== specialisation ================================================ + + function _processRequest(bytes32 id, Request memory req, address feeAsset, uint256 feeValue) internal virtual; + + // =================================================== helpers =================================================== + function _generateRequest( + uint256 chain, + address target, + address sender, + uint256 value, + bytes memory data, + bytes32 salt + ) internal view returns (Request memory) { + return + Request({ + source: Account({chain: block.chainid, instance: sender}), + message: Message({destination: Account({chain: chain, instance: target}), value: value, data: data}), + salt: salt + }); + } + + function _executeRequest(Message memory message) internal { + require(message.destination.chain == block.chainid); + (bool success, bytes memory returndata) = message.destination.instance.call{value: message.value}(message.data); + Address.verifyCallResult(success, returndata); + } +} diff --git a/contracts/crosschain/IGenericGateway.sol b/contracts/crosschain/IGenericGateway.sol index 471919a1..fe824276 100644 --- a/contracts/crosschain/IGenericGateway.sol +++ b/contracts/crosschain/IGenericGateway.sol @@ -33,17 +33,36 @@ interface IGenericGateway { /// @dev Perform a cross-chain call using the canonical payment method for this bridge. The provided value is /// passed along the request, minus anything that would be part of the canonical payment method. - function sendRequest(uint256 chain, address target, bytes memory data, bytes32 salt) external payable returns (bytes32); + function sendRequest( + uint256 chain, + address target, + uint256 value, + bytes memory data, + bytes32 salt + ) external payable returns (bytes32); /// @dev Perform a cross-chain call using the specified payment method. If feeAsset is 0, then feeValue will be /// deduced from the provided value to cover costs. The rest of the value is passed along the request. - function sendRequest(uint256 chain, address target, bytes memory data, bytes32 salt, address feeAsset, uint256 feeValue) external payable returns (bytes32); + function sendRequest( + uint256 chain, + address target, + uint256 value, + bytes memory data, + bytes32 salt, + address feeAsset, + uint256 feeValue + ) external payable returns (bytes32); // ================================================= 2 step mode ================================================= /// @dev Register a cross-chain call that will later be forwarded using {forwardRequest}. Any value passed here /// will be escrowed. It will then be passed along the request be forwarding happens. - function createRequest(uint256 chain, address target, bytes memory data, bytes32 salt) external payable returns (bytes32); + function createRequest( + uint256 chain, + address target, + bytes memory data, + bytes32 salt + ) external payable returns (bytes32); /// @dev Forwards a cross-chain request using the canonical payment method for this bridge. Any value provided /// here will be used for the payment. It will not be forwarded with the cross-chain call. diff --git a/contracts/crosschain/vendor/axelar/interfaces/IAxelarGasService.sol b/contracts/crosschain/vendor/axelar/interfaces/IAxelarGasService.sol index 78ea0f1d..60e363c2 100644 --- a/contracts/crosschain/vendor/axelar/interfaces/IAxelarGasService.sol +++ b/contracts/crosschain/vendor/axelar/interfaces/IAxelarGasService.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.0; -import { GasInfo } from '../types/GasEstimationTypes.sol'; -import { IInterchainGasEstimation } from './IInterchainGasEstimation.sol'; -import { IUpgradable } from './IUpgradable.sol'; +import {GasInfo} from "../types/GasEstimationTypes.sol"; +import {IInterchainGasEstimation} from "./IInterchainGasEstimation.sol"; +import {IUpgradable} from "./IUpgradable.sol"; /** * @title IAxelarGasService Interface @@ -353,11 +353,7 @@ interface IAxelarGasService is IInterchainGasEstimation, IUpgradable { * @param logIndex The log index for the cross-chain call * @param refundAddress The address where refunds, if any, should be sent */ - function addNativeGas( - bytes32 txHash, - uint256 logIndex, - address refundAddress - ) external payable; + function addNativeGas(bytes32 txHash, uint256 logIndex, address refundAddress) external payable; /** * @notice Add additional gas payment using ERC20 tokens after initiating an express cross-chain call. @@ -383,11 +379,7 @@ interface IAxelarGasService is IInterchainGasEstimation, IUpgradable { * @param logIndex The log index for the cross-chain call * @param refundAddress The address where refunds, if any, should be sent */ - function addNativeExpressGas( - bytes32 txHash, - uint256 logIndex, - address refundAddress - ) external payable; + function addNativeExpressGas(bytes32 txHash, uint256 logIndex, address refundAddress) external payable; /** * @notice Updates the gas price for a specific chain. @@ -404,11 +396,7 @@ interface IAxelarGasService is IInterchainGasEstimation, IUpgradable { * @param tokens Array of token addresses to be collected * @param amounts Array of amounts to be collected for each respective token address */ - function collectFees( - address payable receiver, - address[] calldata tokens, - uint256[] calldata amounts - ) external; + function collectFees(address payable receiver, address[] calldata tokens, uint256[] calldata amounts) external; /** * @notice Refunds gas payment to the receiver in relation to a specific cross-chain transaction. @@ -420,13 +408,7 @@ interface IAxelarGasService is IInterchainGasEstimation, IUpgradable { * @param token The token address to be refunded * @param amount The amount to refund */ - function refund( - bytes32 txHash, - uint256 logIndex, - address payable receiver, - address token, - uint256 amount - ) external; + function refund(bytes32 txHash, uint256 logIndex, address payable receiver, address token, uint256 amount) external; /** * @notice Returns the address of the designated gas collector. diff --git a/contracts/crosschain/vendor/axelar/interfaces/IAxelarGateway.sol b/contracts/crosschain/vendor/axelar/interfaces/IAxelarGateway.sol index 92f891cc..bb12c951 100644 --- a/contracts/crosschain/vendor/axelar/interfaces/IAxelarGateway.sol +++ b/contracts/crosschain/vendor/axelar/interfaces/IAxelarGateway.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; -import { IGovernable } from './IGovernable.sol'; -import { IImplementation } from './IImplementation.sol'; +import {IGovernable} from "./IGovernable.sol"; +import {IImplementation} from "./IImplementation.sol"; interface IAxelarGateway is IImplementation, IGovernable { /**********\ @@ -178,15 +178,11 @@ interface IAxelarGateway is IImplementation, IGovernable { function setTokenMintLimits(string[] calldata symbols, uint256[] calldata limits) external; - function upgrade( - address newImplementation, - bytes32 newImplementationCodeHash, - bytes calldata setupParams - ) external; + function upgrade(address newImplementation, bytes32 newImplementationCodeHash, bytes calldata setupParams) external; /**********************\ |* External Functions *| \**********************/ function execute(bytes calldata input) external; -} \ No newline at end of file +} diff --git a/contracts/crosschain/vendor/axelar/interfaces/IImplementation.sol b/contracts/crosschain/vendor/axelar/interfaces/IImplementation.sol index 037b3ce8..ef2631d4 100644 --- a/contracts/crosschain/vendor/axelar/interfaces/IImplementation.sol +++ b/contracts/crosschain/vendor/axelar/interfaces/IImplementation.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -import { IContractIdentifier } from './IContractIdentifier.sol'; +import {IContractIdentifier} from "./IContractIdentifier.sol"; interface IImplementation is IContractIdentifier { error NotProxy(); diff --git a/contracts/crosschain/vendor/axelar/interfaces/IInterchainGasEstimation.sol b/contracts/crosschain/vendor/axelar/interfaces/IInterchainGasEstimation.sol index cab0ba6b..74d4cce8 100644 --- a/contracts/crosschain/vendor/axelar/interfaces/IInterchainGasEstimation.sol +++ b/contracts/crosschain/vendor/axelar/interfaces/IInterchainGasEstimation.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -import { GasEstimationType, GasInfo } from '../types/GasEstimationTypes.sol'; +import {GasEstimationType, GasInfo} from "../types/GasEstimationTypes.sol"; /** * @title IInterchainGasEstimation Interface diff --git a/contracts/crosschain/vendor/axelar/interfaces/IUpgradable.sol b/contracts/crosschain/vendor/axelar/interfaces/IUpgradable.sol index 0ef082fb..c7a14709 100644 --- a/contracts/crosschain/vendor/axelar/interfaces/IUpgradable.sol +++ b/contracts/crosschain/vendor/axelar/interfaces/IUpgradable.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; -import { IOwnable } from './IOwnable.sol'; -import { IImplementation } from './IImplementation.sol'; +import {IOwnable} from "./IOwnable.sol"; +import {IImplementation} from "./IImplementation.sol"; // General interface for upgradable contracts interface IUpgradable is IOwnable, IImplementation { @@ -15,9 +15,5 @@ interface IUpgradable is IOwnable, IImplementation { function implementation() external view returns (address); - function upgrade( - address newImplementation, - bytes32 newImplementationCodeHash, - bytes calldata params - ) external; + function upgrade(address newImplementation, bytes32 newImplementationCodeHash, bytes calldata params) external; } diff --git a/contracts/utils/Set.sol b/contracts/utils/Set.sol index 5ad58f30..1a952162 100644 --- a/contracts/utils/Set.sol +++ b/contracts/utils/Set.sol @@ -29,4 +29,4 @@ library Set { function contains(Bytes32Set storage self, bytes32 value) internal view returns (bool) { return self._data[value]; } -} \ No newline at end of file +} From 28e88cdba3be5e59e2e765449ff66cedae05204f Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Sun, 23 Jun 2024 14:23:45 +0200 Subject: [PATCH 03/51] add relay observability --- contracts/crosschain/GenericGatewayAxelar.sol | 1 + contracts/crosschain/GenericGatewayCommon.sol | 30 +++++++++++++++++-- contracts/crosschain/IGenericGateway.sol | 5 ++++ lib/@openzeppelin-contracts | 2 +- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/contracts/crosschain/GenericGatewayAxelar.sol b/contracts/crosschain/GenericGatewayAxelar.sol index ed7d7ac7..eebeb5fb 100644 --- a/contracts/crosschain/GenericGatewayAxelar.sol +++ b/contracts/crosschain/GenericGatewayAxelar.sol @@ -18,6 +18,7 @@ contract GenericGatewayAxelar is GenericGatewayCommon, Ownable { string name; string remote; } + mapping(uint256 chainId => ChainDetails chainName) public chainDetails; constructor(IAxelarGateway _gateway, address _initialOwner) Ownable(_initialOwner) { diff --git a/contracts/crosschain/GenericGatewayCommon.sol b/contracts/crosschain/GenericGatewayCommon.sol index e1a5b312..6fedfbfc 100644 --- a/contracts/crosschain/GenericGatewayCommon.sol +++ b/contracts/crosschain/GenericGatewayCommon.sol @@ -7,8 +7,12 @@ import {Set} from "../utils/Set.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {SlotDerivation} from "@openzeppelin/contracts@master/utils/SlotDerivation.sol"; +import {StorageSlot} from "@openzeppelin/contracts@master/utils/StorageSlot.sol"; abstract contract GenericGatewayCommon is IGenericGateway { + using SlotDerivation for *; + using StorageSlot for *; using Set for Set.Bytes32Set; event RequestCreated(bytes32 id, Request req); @@ -17,6 +21,17 @@ abstract contract GenericGatewayCommon is IGenericGateway { Set.Bytes32Set private _outBox; + // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.GenericGatewayCommon")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant GENERIC_GATEWAY_COMMON_STORAGE = + 0x71ab59e9fe1edd5f3d56389a2c715a90ddbb606dacc41e1c5360c10e3fe15b00; + + function crossChainSender() public view returns (uint256 chainId, address sender) { + return ( + GENERIC_GATEWAY_COMMON_STORAGE.offset(0).asUint256().tload(), + GENERIC_GATEWAY_COMMON_STORAGE.offset(1).asAddress().tload() + ); + } + // This must be redeclared as public so that other function can call it function defaultCost(Message memory /*message*/) public pure virtual returns (address, uint256); @@ -131,9 +146,18 @@ abstract contract GenericGatewayCommon is IGenericGateway { }); } - function _executeRequest(Message memory message) internal { - require(message.destination.chain == block.chainid); - (bool success, bytes memory returndata) = message.destination.instance.call{value: message.value}(message.data); + function _executeRequest(Request memory req) internal { + require(req.message.destination.chain == block.chainid); + + GENERIC_GATEWAY_COMMON_STORAGE.offset(0).asUint256().tstore(req.source.chain); + GENERIC_GATEWAY_COMMON_STORAGE.offset(1).asAddress().tstore(req.source.instance); + + (bool success, bytes memory returndata) = req.message.destination.instance.call{value: req.message.value}( + req.message.data + ); Address.verifyCallResult(success, returndata); + + GENERIC_GATEWAY_COMMON_STORAGE.offset(0).asUint256().tstore(0); + GENERIC_GATEWAY_COMMON_STORAGE.offset(1).asAddress().tstore(address(0)); } } diff --git a/contracts/crosschain/IGenericGateway.sol b/contracts/crosschain/IGenericGateway.sol index fe824276..84d58e2b 100644 --- a/contracts/crosschain/IGenericGateway.sol +++ b/contracts/crosschain/IGenericGateway.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; interface IGenericGateway { + // TODO: use uint96 chain id so that Account fit in a single word ? struct Account { uint256 chain; address instance; @@ -22,6 +23,10 @@ interface IGenericGateway { bytes32 salt; } + // ============================================ relay instrumentation ============================================ + + function crossChainSender() external view returns (uint256 chainId, address sender); + // =============================================== cost estimation =============================================== function defaultCost(Message memory message) external view returns (address, uint256); diff --git a/lib/@openzeppelin-contracts b/lib/@openzeppelin-contracts index 52c36d41..c3f8b760 160000 --- a/lib/@openzeppelin-contracts +++ b/lib/@openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 52c36d412e8681053975396223d0ea39687fe33b +Subproject commit c3f8b760ad9d0431f33e8303658ccbdcc1610f24 From 948dab4b4e1889844ffb1a406ec075cfca745187 Mon Sep 17 00:00:00 2001 From: ernestognw Date: Tue, 30 Jul 2024 11:14:16 -0600 Subject: [PATCH 04/51] Update oz to master --- lib/@openzeppelin-contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/@openzeppelin-contracts b/lib/@openzeppelin-contracts index c3f8b760..5480641e 160000 --- a/lib/@openzeppelin-contracts +++ b/lib/@openzeppelin-contracts @@ -1 +1 @@ -Subproject commit c3f8b760ad9d0431f33e8303658ccbdcc1610f24 +Subproject commit 5480641e5c572fc7f9d68d59003f4b6417168cdd From e350890c98501d0a6da59233a39e5d9bf1ca79d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 13 Aug 2024 00:20:35 -0600 Subject: [PATCH 05/51] Iterate --- contracts/crosschain/Gateway.sol | 110 ++++++++++++++++++ contracts/crosschain/GenericGatewayCommon.sol | 3 + contracts/crosschain/IGateway.sol | 95 +++++++++++++++ contracts/crosschain/IGenericGateway.sol | 80 ------------- lib/@openzeppelin-contracts | 2 +- 5 files changed, 209 insertions(+), 81 deletions(-) create mode 100644 contracts/crosschain/Gateway.sol create mode 100644 contracts/crosschain/IGateway.sol delete mode 100644 contracts/crosschain/IGenericGateway.sol diff --git a/contracts/crosschain/Gateway.sol b/contracts/crosschain/Gateway.sol new file mode 100644 index 00000000..aca74ba5 --- /dev/null +++ b/contracts/crosschain/Gateway.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {IGatewayDestination, IGatewaySource, IGateway} from "./IGateway.sol"; +import {Set} from "../utils/Set.sol"; + +/// @dev Generic implementation of a Gateway contract on the source chain according to ERC-XXXX definitions. +abstract contract GatewaySource is IGatewaySource { + using Set for Set.Bytes32Set; + Set.Bytes32Set private _createdBox; + Set.Bytes32Set private _sentBox; + + /// @inheritdoc IGateway + function messageId(Message memory message) public pure virtual returns (bytes32) { + return keccak256(abi.encode(message)); + } + + /// @inheritdoc IGatewaySource + function sendingStatus(bytes32 id) public view virtual returns (MessageSourceStatus) { + if (_sentBox.contains(id)) return MessageSourceStatus.Sent; + if (_createdBox.contains(id)) return MessageSourceStatus.Created; + return MessageSourceStatus.Unknown; + } + + /// @inheritdoc IGatewaySource + function sendRequest(Message memory message) external payable virtual returns (bytes32) { + return _sendRequest(messageId(message), message); + } + + /// @inheritdoc IGatewaySource + function createRequest(Message memory message) external payable virtual returns (bytes32) { + bytes32 id = messageId(message); + + // Check if the message was already sent or created. + if (sendingStatus(id) == MessageSourceStatus.Unknown) revert DuplicatedSourceMessage(id); + + emit MessageCreated(id, message); + return id; + } + + /// @inheritdoc IGatewaySource + function forwardRequest(Message memory message) external payable virtual returns (bytes32) { + bytes32 id = messageId(message); + if (!_createdBox.contains(id)) revert UnknownMessage(id); + return _sendRequest(id, message); + } + + function _sendRequest(bytes32 id, Message memory message) internal virtual returns (bytes32) { + // Check if the message was already sent. + if (_createdBox.insert(id)) revert DuplicatedSourceMessage(id); + + _processSend(id, message); + emit MessageSent(id, message); + return id; + } + + /// @dev Process a cross-chain message sent. Its up to the gateway to implement the logic. + function _processSend(bytes32 id, Message memory message) internal virtual; +} + +abstract contract GatewayDestination is IGatewayDestination { + using Set for Set.Bytes32Set; + Set.Bytes32Set private _executedBox; + Set.Bytes32Set private _deliveredBox; + + /// @inheritdoc IGateway + function messageId(Message memory message) public pure virtual returns (bytes32) { + return keccak256(abi.encode(message)); + } + + /// @inheritdoc IGatewayDestination + function destinationStatus(bytes32 id) public view virtual returns (MessageDestinationStatus) { + if (_executedBox.contains(id)) return MessageDestinationStatus.Executed; + if (_deliveredBox.contains(id)) return MessageDestinationStatus.Delivered; + return MessageDestinationStatus.Unknown; + } + + /// @inheritdoc IGatewayDestination + function setRequestDelivered(Message memory message) external virtual returns (bytes32) { + bytes32 id = messageId(message); + + // Check if the message was already delivered or executed. + if (destinationStatus(id) == MessageDestinationStatus.Unknown) revert DuplicatedDestinationMessage(id); + + _processDelivery(id, message); + emit MessageDelivered(id, message); + return id; + } + + /// @inheritdoc IGatewayDestination + function setRequestExecuted(Message memory message) external virtual returns (bytes32) { + bytes32 id = messageId(message); + if (!_executedBox.insert(id)) revert DuplicatedDestinationMessage(id); + emit MessageExecuted(id, message); + return id; + } + + /// @dev Process a cross-chain message delivery. Its up to the gateway to implement the logic. + function _processDelivery(bytes32 id, Message memory message) internal virtual; +} + +/// @dev Generic implementation of a Gateway contract according to ERC-XXXX definitions. +abstract contract Gateway is GatewayDestination, GatewaySource { + function messageId( + Message memory message + ) public pure override(GatewayDestination, GatewaySource) returns (bytes32) { + return super.messageId(message); + } +} diff --git a/contracts/crosschain/GenericGatewayCommon.sol b/contracts/crosschain/GenericGatewayCommon.sol index 6fedfbfc..b0ebd145 100644 --- a/contracts/crosschain/GenericGatewayCommon.sol +++ b/contracts/crosschain/GenericGatewayCommon.sol @@ -10,6 +10,9 @@ import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeE import {SlotDerivation} from "@openzeppelin/contracts@master/utils/SlotDerivation.sol"; import {StorageSlot} from "@openzeppelin/contracts@master/utils/StorageSlot.sol"; +/** + * @dev Generic implementation of a Gateway contract according to ERC-XXXX definitions. + */ abstract contract GenericGatewayCommon is IGenericGateway { using SlotDerivation for *; using StorageSlot for *; diff --git a/contracts/crosschain/IGateway.sol b/contracts/crosschain/IGateway.sol new file mode 100644 index 00000000..c3b36e19 --- /dev/null +++ b/contracts/crosschain/IGateway.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/// @dev Interface for a generic cross-chain gateway. +/// The gateway is responsible for sending and receiving messages between different blockchains. +interface IGateway { + /// @dev Represents an account on a specific chain. Might be a contract. + struct Account { + uint256 chain; + address instance; + } + + /// @dev Represents a cross-chain message. + struct Message { + Account source; + Account destination; + // Native token value to be sent with the message. + uint256 value; + // To dissambiguate between duplicated messages. + bytes32 salt; + // Arbitrary data to be sent with the message. + bytes payload; + // Extra parameters to be used by the gateway specialization. + bytes extraParams; + } + + /// @dev Uniquely identifies a message. + function messageId(Message memory message) external pure returns (bytes32); +} + +/// @dev Interface for a cross-chain gateway that sends messages. +/// Allows for 2 sending modes: {sendRequest} and {createRequest} + {forwardRequest}, +/// where the latter allows to hook logic before sending the message. +interface IGatewaySource is IGateway { + enum MessageSourceStatus { + Unknown, + Created, + Sent + } + + event MessageCreated(bytes32 indexed id, Message message); + event MessageSent(bytes32 indexed id, Message message); + + /// @dev Emitted when a message is created with the same id as a previous one (either created or sent). + error DuplicatedSourceMessage(bytes32 id); + + /// @dev Emitted when trying to forward a message that was not created. + error UnknownMessage(bytes32 id); + + /// @dev Returns the status of a sent cross-chain request. + function sendingStatus(bytes32 id) external view returns (MessageSourceStatus); + + /// @dev Send a cross-chain message to the target chain. + /// MessageSourceStatus.Unknown -> MessageSourceStatus.Sent + function sendRequest(Message memory message) external payable returns (bytes32); + + /// @dev Create a cross-chain message to the target chain. See {forwardRequest} to send it. + /// MessageSourceStatus.Unknown -> MessageSourceStatus.Created + function createRequest(Message memory message) external payable returns (bytes32); + + /// @dev Forwards a previously created cross-chain message to the target chain. See {createRequest} to create it. + /// MessageSourceStatus.Created -> MessageSourceStatus.Sent + function forwardRequest(Message memory message) external payable returns (bytes32); +} + +/// @dev Interface for a cross-chain gateway that receives messages. +/// Allows to check the status of a message and to mark it as delivered or executed. +interface IGatewayDestination is IGateway { + enum MessageDestinationStatus { + Unknown, + Delivered, + Executed + } + + event MessageDelivered(bytes32 indexed id, Message message); + event MessageExecuted(bytes32 indexed id, Message message); + + /// @dev Emitted when a message is delivered with the same id as a previous one (either delivered or executed). + error DuplicatedDestinationMessage(bytes32 id); + + /// @dev Returns the status of a received cross-chain request. + function destinationStatus(bytes32 id) external view returns (MessageDestinationStatus); + + /// @dev Sets a cross-chain request as delivered, ready for execution. + /// MessageDestinationStatus.Unknown -> MessageDestinationStatus.Delivered + /// NOTE: Should only be called by an authorized gateway operator. + function setRequestDelivered(Message memory message) external returns (bytes32); + + /// @dev Marks a cross-chain request as executed. + /// MessageDestinationStatus.Unknown -> MessageDestinationStatus.Executed + /// MessageDestinationStatus.Delivered -> MessageDestinationStatus.Executed + /// NOTE: Should only be called by the destination account. + function setRequestExecuted(Message memory message) external returns (bytes32); +} diff --git a/contracts/crosschain/IGenericGateway.sol b/contracts/crosschain/IGenericGateway.sol deleted file mode 100644 index 84d58e2b..00000000 --- a/contracts/crosschain/IGenericGateway.sol +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -interface IGenericGateway { - // TODO: use uint96 chain id so that Account fit in a single word ? - struct Account { - uint256 chain; - address instance; - } - - // TODO: (address,uint256)[] tokens? - // TODO: put value in tokens? - struct Message { - Account destination; - uint256 value; - bytes data; - } - - struct Request { - Account source; - Message message; - bytes32 salt; - } - - // ============================================ relay instrumentation ============================================ - - function crossChainSender() external view returns (uint256 chainId, address sender); - - // =============================================== cost estimation =============================================== - - function defaultCost(Message memory message) external view returns (address, uint256); - - /// @dev Returns the (minimum) cost (in a given asset) of performing a cross-chain operation. If asset is not supported for payment, returns type(uint256).max - function estimateCost(Message memory message, address asset) external view returns (uint256); - - // ================================================= 1 step mode ================================================= - - /// @dev Perform a cross-chain call using the canonical payment method for this bridge. The provided value is - /// passed along the request, minus anything that would be part of the canonical payment method. - function sendRequest( - uint256 chain, - address target, - uint256 value, - bytes memory data, - bytes32 salt - ) external payable returns (bytes32); - - /// @dev Perform a cross-chain call using the specified payment method. If feeAsset is 0, then feeValue will be - /// deduced from the provided value to cover costs. The rest of the value is passed along the request. - function sendRequest( - uint256 chain, - address target, - uint256 value, - bytes memory data, - bytes32 salt, - address feeAsset, - uint256 feeValue - ) external payable returns (bytes32); - - // ================================================= 2 step mode ================================================= - - /// @dev Register a cross-chain call that will later be forwarded using {forwardRequest}. Any value passed here - /// will be escrowed. It will then be passed along the request be forwarding happens. - function createRequest( - uint256 chain, - address target, - bytes memory data, - bytes32 salt - ) external payable returns (bytes32); - - /// @dev Forwards a cross-chain request using the canonical payment method for this bridge. Any value provided - /// here will be used for the payment. It will not be forwarded with the cross-chain call. - function forwardRequest(Request memory req) external payable; - - /// @dev Forwards a cross-chain request using using the specified payment method. Any value provided here will be - /// used for the payment. It will not be forwarded with the cross-chain call. This means that value should only be - /// used with `feeAsset = address(0)` and with `feeValue = msg.value`. - function forwardRequest(Request memory req, address feeAsset, uint256 feeValue) external payable; -} diff --git a/lib/@openzeppelin-contracts b/lib/@openzeppelin-contracts index 5480641e..52c36d41 160000 --- a/lib/@openzeppelin-contracts +++ b/lib/@openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 5480641e5c572fc7f9d68d59003f4b6417168cdd +Subproject commit 52c36d412e8681053975396223d0ea39687fe33b From de9da705bb788ef2b07cbfe43a247d811abbb2bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 13 Aug 2024 09:25:58 -0600 Subject: [PATCH 06/51] Remove salt --- contracts/crosschain/IGateway.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/crosschain/IGateway.sol b/contracts/crosschain/IGateway.sol index c3b36e19..5d81a8e2 100644 --- a/contracts/crosschain/IGateway.sol +++ b/contracts/crosschain/IGateway.sol @@ -17,8 +17,6 @@ interface IGateway { Account destination; // Native token value to be sent with the message. uint256 value; - // To dissambiguate between duplicated messages. - bytes32 salt; // Arbitrary data to be sent with the message. bytes payload; // Extra parameters to be used by the gateway specialization. From d76074284699466eecb13931233ffa5ab04cf2a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 13 Aug 2024 18:10:16 -0600 Subject: [PATCH 07/51] Iterate --- contracts/crosschain/Gateway.sol | 206 +++++++++++++++++++++++------- contracts/crosschain/IGateway.sol | 81 +++++++----- contracts/utils/CAIP-10.sol | 62 +++++++++ contracts/utils/CAIP-2.sol | 64 ++++++++++ 4 files changed, 334 insertions(+), 79 deletions(-) create mode 100644 contracts/utils/CAIP-10.sol create mode 100644 contracts/utils/CAIP-2.sol diff --git a/contracts/crosschain/Gateway.sol b/contracts/crosschain/Gateway.sol index aca74ba5..3a5921c9 100644 --- a/contracts/crosschain/Gateway.sol +++ b/contracts/crosschain/Gateway.sol @@ -2,18 +2,30 @@ pragma solidity ^0.8.0; -import {IGatewayDestination, IGatewaySource, IGateway} from "./IGateway.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {IGatewayDestination, IGatewaySource, IGatewayBase} from "./IGateway.sol"; import {Set} from "../utils/Set.sol"; +import {CAIP10} from "../utils/CAIP-10.sol"; +import {CAIP2} from "../utils/CAIP-2.sol"; -/// @dev Generic implementation of a Gateway contract on the source chain according to ERC-XXXX definitions. +/// @dev Gateway contract on the source chain according to ERC-XXXX definitions. +/// +/// This is a generic implementation of an asynchronous message-passing system between accounts on different chains. abstract contract GatewaySource is IGatewaySource { using Set for Set.Bytes32Set; + using CAIP2 for CAIP2.ChainId; + using CAIP10 for CAIP10.Account; + Set.Bytes32Set private _createdBox; Set.Bytes32Set private _sentBox; - /// @inheritdoc IGateway - function messageId(Message memory message) public pure virtual returns (bytes32) { - return keccak256(abi.encode(message)); + /// @inheritdoc IGatewayBase + function messageId( + string memory source, + string memory destination, + Message memory message + ) public pure virtual returns (bytes32) { + return keccak256(abi.encode(source, destination, message)); } /// @inheritdoc IGatewaySource @@ -24,39 +36,95 @@ abstract contract GatewaySource is IGatewaySource { } /// @inheritdoc IGatewaySource - function sendRequest(Message memory message) external payable virtual returns (bytes32) { - return _sendRequest(messageId(message), message); + function sendMessage( + string memory source, + string memory destination, + Message memory message + ) external payable virtual returns (bytes32) { + _authorizeSendingMessage(source, source, message); + bytes32 id = messageId(source, destination, message); + return _sendMessage(id, sendingStatus(id), message); } /// @inheritdoc IGatewaySource - function createRequest(Message memory message) external payable virtual returns (bytes32) { - bytes32 id = messageId(message); + function createMessage( + string memory source, + string memory destination, + Message memory message + ) external payable virtual returns (bytes32) { + _authorizeSendingMessage(source, message); + bytes32 id = messageId(source, destination, message); + return _createMessage(id, sendingStatus(id), message); + } + + /// @inheritdoc IGatewaySource + function forwardMessage( + string memory source, + string memory destination, + Message memory message + ) external payable virtual returns (bytes32) { + bytes32 id = messageId(source, destination, message); + return _forwardMessage(id, sendingStatus(id), message); + } - // Check if the message was already sent or created. - if (sendingStatus(id) == MessageSourceStatus.Unknown) revert DuplicatedSourceMessage(id); + function _authorizeSendingMessage(string memory source, Message memory message) internal virtual { + CAIP10.Account memory sourceAccount = CAIP10.fromString(source); - emit MessageCreated(id, message); - return id; + // Sender must match the source account + bool isSelf = Strings.equal(Strings.toHexString(msg.sender), sourceAccount._accountId); + + if (!isSelf || !_EVMValidity()) { + revert UnauthorizedSourceMessage(source, msg.sender, message); + } } - /// @inheritdoc IGatewaySource - function forwardRequest(Message memory message) external payable virtual returns (bytes32) { - bytes32 id = messageId(message); - if (!_createdBox.contains(id)) revert UnknownMessage(id); - return _sendRequest(id, message); + function _EVMValidity(CAIP10.Account memory sourceAccount) private pure { + return + sourceAccount._chainId._namespace == _chainId() && // Chain ID must match the current chain + sourceAccount._chainId._reference == bytes32(bytes(string("eip155"))); // EIP-155 for EVM chains } - function _sendRequest(bytes32 id, Message memory message) internal virtual returns (bytes32) { - // Check if the message was already sent. - if (_createdBox.insert(id)) revert DuplicatedSourceMessage(id); + function _createMessage(bytes32 id, MessageSourceStatus status, Message memory message) private returns (bytes32) { + // Check if the message was not created or sent before. NOOP otherwise. + if (status == MessageSourceStatus.Unknown) { + _createdBox.insert(id); + emit MessageCreated(id, message); + } + return id; + } + + function _sendMessage(bytes32 id, MessageSourceStatus status, Message memory message) private returns (bytes32) { + /// Check if the message hwas not sent before. NOOP otherwise. + if (status != MessageSourceStatus.Sent) { + _sentBox.insert(id); + emit MessageSent(id, message); + } + return id; + } - _processSend(id, message); - emit MessageSent(id, message); + function _forwardMessage(bytes32 id, MessageSourceStatus status, Message memory message) private returns (bytes32) { + // Check if the message was created first. NOOP otherwise. + if (status == MessageSourceStatus.Created) { + _sendMessage(id, status, message); + } return id; } - /// @dev Process a cross-chain message sent. Its up to the gateway to implement the logic. - function _processSend(bytes32 id, Message memory message) internal virtual; + /// @dev Returns the chain ID of the current chain. + /// Assumes block.chainId < type(uint64).max + function _chainId() private view returns (bytes8 chainId) { + unchecked { + uint256 id = block.chainid; + while (true) { + chainId--; + assembly ("memory-safe") { + mstore8(chainId, byte(mod(id, 10), HEX_DIGITS)) + } + id /= 10; + if (id == 0) break; + } + } + } } abstract contract GatewayDestination is IGatewayDestination { @@ -64,9 +132,13 @@ abstract contract GatewayDestination is IGatewayDestination { Set.Bytes32Set private _executedBox; Set.Bytes32Set private _deliveredBox; - /// @inheritdoc IGateway - function messageId(Message memory message) public pure virtual returns (bytes32) { - return keccak256(abi.encode(message)); + /// @inheritdoc IGatewayBase + function messageId( + string memory source, + string memory destination, + Message memory message + ) public pure virtual returns (bytes32) { + return keccak256(abi.encode(source, destination, message)); } /// @inheritdoc IGatewayDestination @@ -77,34 +149,80 @@ abstract contract GatewayDestination is IGatewayDestination { } /// @inheritdoc IGatewayDestination - function setRequestDelivered(Message memory message) external virtual returns (bytes32) { - bytes32 id = messageId(message); + function deliverMessage( + string memory source, + string memory destination, + Message memory message + ) external virtual returns (bytes32) { + bytes32 id = messageId(source, destination, message); + _authorizeDeliveringMessage(destination, message); + MessageDestinationStatus status = destinationStatus(id); + return _deliverMessage(id, status, message); + } - // Check if the message was already delivered or executed. - if (destinationStatus(id) == MessageDestinationStatus.Unknown) revert DuplicatedDestinationMessage(id); + /// @inheritdoc IGatewayDestination + function setMessageExecuted( + string memory source, + string memory destination, + Message memory message + ) external virtual returns (bytes32) { + bytes32 id = messageId(source, destination, message); + MessageDestinationStatus status = destinationStatus(id); + return _setMessageExecuted(id, status, message); + } - _processDelivery(id, message); - emit MessageDelivered(id, message); - return id; + /// @dev Authorizes the delivery of a message to the destination chain. + function _authorizeDeliveringMessage(string memory destination, Message memory message) internal virtual { + CAIP10.Account memory destinationAccount = CAIP10.fromString(destination); + + if (_validateMessage(message) && !_EVMValidity(destinationAccount)) { + revert UnauthorizedDestinationMessage(destination, msg.sender, message); + } } - /// @inheritdoc IGatewayDestination - function setRequestExecuted(Message memory message) external virtual returns (bytes32) { - bytes32 id = messageId(message); - if (!_executedBox.insert(id)) revert DuplicatedDestinationMessage(id); - emit MessageExecuted(id, message); + /// @dev Validates the message before delivering it. Left unimplemented to allow for custom access control. + function _validateMessage(Message memory message) internal virtual; + + function _deliverMessage( + bytes32 id, + MessageDestinationStatus status, + Message memory message + ) private returns (bytes32) { + // Check if the message was not delivered or executed before. NOOP otherwise. + if (status == MessageDestinationStatus.Unknown) { + _deliveredBox.insert(id); + emit MessageDelivered(id, message); + } return id; } - /// @dev Process a cross-chain message delivery. Its up to the gateway to implement the logic. - function _processDelivery(bytes32 id, Message memory message) internal virtual; + function _EVMValidity(CAIP10.Account memory destinationAccount) private pure { + return + destinationAccount._chainId._namespace == _chainId() && // Chain ID must match the current chain + destinationAccount._chainId._reference == bytes32(bytes(string("eip155"))); // EIP-155 for EVM chains + } + + function _setMessageExecuted( + bytes32 id, + MessageDestinationStatus status, + Message memory message + ) private returns (bytes32) { + // Check if the message was not executed already. NOOP otherwise. + if (status != MessageDestinationStatus.Executed) { + _executedBox.insert(id); + emit MessageExecuted(id, message); + } + return id; + } } /// @dev Generic implementation of a Gateway contract according to ERC-XXXX definitions. abstract contract Gateway is GatewayDestination, GatewaySource { function messageId( + string memory source, + string memory destination, Message memory message - ) public pure override(GatewayDestination, GatewaySource) returns (bytes32) { - return super.messageId(message); + ) public pure override(GatewaySource, GatewayDestination) returns (bytes32) { + return super.messageId(source, destination, message); } } diff --git a/contracts/crosschain/IGateway.sol b/contracts/crosschain/IGateway.sol index 5d81a8e2..3fb6cadd 100644 --- a/contracts/crosschain/IGateway.sol +++ b/contracts/crosschain/IGateway.sol @@ -4,17 +4,9 @@ pragma solidity ^0.8.0; /// @dev Interface for a generic cross-chain gateway. /// The gateway is responsible for sending and receiving messages between different blockchains. -interface IGateway { - /// @dev Represents an account on a specific chain. Might be a contract. - struct Account { - uint256 chain; - address instance; - } - +interface IGatewayBase { /// @dev Represents a cross-chain message. struct Message { - Account source; - Account destination; // Native token value to be sent with the message. uint256 value; // Arbitrary data to be sent with the message. @@ -24,13 +16,19 @@ interface IGateway { } /// @dev Uniquely identifies a message. - function messageId(Message memory message) external pure returns (bytes32); + /// @param source CAIP-10 account ID of the source chain. + /// @param destination CAIP-10 account ID of the destination chain. + function messageId( + string memory source, + string memory destination, + Message memory message + ) external pure returns (bytes32); } /// @dev Interface for a cross-chain gateway that sends messages. -/// Allows for 2 sending modes: {sendRequest} and {createRequest} + {forwardRequest}, +/// Allows for 2 sending modes: {sendMessage} and {createMessage} + {forwardMessage}, /// where the latter allows to hook logic before sending the message. -interface IGatewaySource is IGateway { +interface IGatewaySource is IGatewayBase { enum MessageSourceStatus { Unknown, Created, @@ -40,31 +38,39 @@ interface IGatewaySource is IGateway { event MessageCreated(bytes32 indexed id, Message message); event MessageSent(bytes32 indexed id, Message message); - /// @dev Emitted when a message is created with the same id as a previous one (either created or sent). - error DuplicatedSourceMessage(bytes32 id); - - /// @dev Emitted when trying to forward a message that was not created. - error UnknownMessage(bytes32 id); + error UnauthorizedSourceMessage(string source, address sender, Message message); - /// @dev Returns the status of a sent cross-chain request. + /// @dev Returns the status of a sent cross-chain message. function sendingStatus(bytes32 id) external view returns (MessageSourceStatus); /// @dev Send a cross-chain message to the target chain. /// MessageSourceStatus.Unknown -> MessageSourceStatus.Sent - function sendRequest(Message memory message) external payable returns (bytes32); + function sendMessage( + string memory source, + string memory destination, + Message memory message + ) external payable returns (bytes32); - /// @dev Create a cross-chain message to the target chain. See {forwardRequest} to send it. + /// @dev Create a cross-chain message to the target chain. See {forwardMessage} to send it. /// MessageSourceStatus.Unknown -> MessageSourceStatus.Created - function createRequest(Message memory message) external payable returns (bytes32); + function createMessage( + string memory source, + string memory destination, + Message memory message + ) external payable returns (bytes32); - /// @dev Forwards a previously created cross-chain message to the target chain. See {createRequest} to create it. + /// @dev Forwards a previously created cross-chain message to the target chain. See {createMessage} to create it. /// MessageSourceStatus.Created -> MessageSourceStatus.Sent - function forwardRequest(Message memory message) external payable returns (bytes32); + function forwardMessage( + string memory source, + string memory destination, + Message memory message + ) external payable returns (bytes32); } /// @dev Interface for a cross-chain gateway that receives messages. /// Allows to check the status of a message and to mark it as delivered or executed. -interface IGatewayDestination is IGateway { +interface IGatewayDestination is IGatewayBase { enum MessageDestinationStatus { Unknown, Delivered, @@ -74,20 +80,25 @@ interface IGatewayDestination is IGateway { event MessageDelivered(bytes32 indexed id, Message message); event MessageExecuted(bytes32 indexed id, Message message); - /// @dev Emitted when a message is delivered with the same id as a previous one (either delivered or executed). - error DuplicatedDestinationMessage(bytes32 id); - - /// @dev Returns the status of a received cross-chain request. + /// @dev Returns the status of a received cross-chain message. function destinationStatus(bytes32 id) external view returns (MessageDestinationStatus); - /// @dev Sets a cross-chain request as delivered, ready for execution. + /// @dev Sets a cross-chain message as delivered, ready for execution. /// MessageDestinationStatus.Unknown -> MessageDestinationStatus.Delivered /// NOTE: Should only be called by an authorized gateway operator. - function setRequestDelivered(Message memory message) external returns (bytes32); - - /// @dev Marks a cross-chain request as executed. - /// MessageDestinationStatus.Unknown -> MessageDestinationStatus.Executed - /// MessageDestinationStatus.Delivered -> MessageDestinationStatus.Executed + function deliverMessage( + string memory source, + string memory destination, + Message memory message + ) external returns (bytes32); + + /// @dev Marks a cross-chain message as executed. + /// MessageDestinationStatus.Unknown -> MessageDestinationStatus.Executed. + /// MessageDestinationStatus.Delivered -> MessageDestinationStatus.Executed. /// NOTE: Should only be called by the destination account. - function setRequestExecuted(Message memory message) external returns (bytes32); + function setMessageExecuted( + string memory source, + string memory destination, + Message memory message + ) external returns (bytes32); } diff --git a/contracts/utils/CAIP-10.sol b/contracts/utils/CAIP-10.sol new file mode 100644 index 00000000..687d54cf --- /dev/null +++ b/contracts/utils/CAIP-10.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {CAIP2} from "./CAIP-2.sol"; + +library CAIP10 { + using CAIP2 for CAIP2.ChainId; + + // account_id: chain_id + ":" + account_address + // chain_id: [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See [CAIP-2][]) + // account_address: [-.%a-zA-Z0-9]{1,128} + struct Account { + CAIP2.ChainId _chainId; + string _accountId; // Often referred to as address + } + + function toString(Account memory account) internal pure returns (string memory) { + return string(abi.encodePacked(account._chainId.toString(), CAIP2.SEMICOLON, account._accountId)); + } + + function fromString(string memory accountStr) internal pure returns (Account memory account) { + bytes memory accountBuffer = bytes(accountStr); + uint256 lastSeparatorIndex = _findLastSeparatorIndex(accountBuffer); + account._chainId = extractChainId(accountBuffer, lastSeparatorIndex); + account._accountId = extractAccountId(accountBuffer, lastSeparatorIndex); + return account; + } + + function extractChainId( + bytes memory accountBuffer, + uint256 lastSeparatorIndex + ) internal pure returns (CAIP2.ChainId memory chainId) { + bytes memory _chainId = new bytes(lastSeparatorIndex); + for (uint256 i = 0; i < lastSeparatorIndex; i++) { + _chainId[i] = accountBuffer[i]; + } + return CAIP2.fromString(string(_chainId)); + } + + function extractAccountId( + bytes memory accountBuffer, + uint256 lastSeparatorIndex + ) internal pure returns (string memory) { + uint256 length = accountBuffer.length; + uint256 offset = lastSeparatorIndex - 1; + bytes memory _accountId = new bytes(length - offset); // Will overflow if no separator is found + for (uint256 i = lastSeparatorIndex + 1; i < length; i++) { + _accountId[i - offset] = accountBuffer[i]; + } + return string(_accountId); + } + + function _findLastSeparatorIndex(bytes memory accountBuffer) private pure returns (uint256) { + for (uint256 i = accountBuffer.length - 1; i >= 0; i--) { + if (accountBuffer[i] == CAIP2.SEMICOLON) { + return i; + } + } + return 0; + } +} diff --git a/contracts/utils/CAIP-2.sol b/contracts/utils/CAIP-2.sol new file mode 100644 index 00000000..61e5faee --- /dev/null +++ b/contracts/utils/CAIP-2.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {Arrays} from "@openzeppelin/contracts/utils/Arrays.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + +library CAIP2 { + // chain_id: namespace + ":" + reference + // namespace: [-a-z0-9]{3,8} + // reference: [-_a-zA-Z0-9]{1,32} + struct ChainId { + bytes8 _namespace; + bytes32 _reference; + } + + bytes1 constant SEMICOLON = ":"; + + error TooLongChainId(); + + function toString(ChainId memory chainId) internal pure returns (string memory) { + return string(abi.encodePacked(chainId._namespace, SEMICOLON, chainId._reference)); + } + + /// @dev Parses a chain ID from a string by splitting it at the first semicolon. + /// The function parses both sides as `bytes8` and `bytes32` respectively wiothout any validation. + function fromString(string memory chainStr) internal pure returns (ChainId memory chainId) { + bytes memory chainBuffer = bytes(chainStr); + uint8 semicolonIndex = _findSemicolonIndex(chainBuffer); + chainId._namespace = _extractNamespace(chainBuffer, semicolonIndex); + chainId._reference = _unsafeExtractReference(chainBuffer, semicolonIndex); + return chainId; + } + + /// @dev Extracts the first `semicolonIndex` bytes from the chain buffer as a bytes8 namespace. + function _extractNamespace(bytes memory chainBuffer, uint8 semicolonIndex) private pure returns (bytes8 namespace) { + assembly ("memory-safe") { + let shift := sub(256, mul(semicolonIndex, 8)) + namespace := shl(shift, shr(shift, mload(add(chainBuffer, 0x20)))) + } + } + + /// @dev Extracts the reference from the chain buffer after the semicolon located at `offset`. + /// + /// IMPORTANT: The caller must make sure that the semicolon index is within the chain buffer length + /// and that there are 32 bytes available after the semicolon. Otherwise dirty memory could be read. + function _unsafeExtractReference(bytes memory chainBuffer, uint8 offset) private pure returns (bytes32 ref) { + assembly ("memory-safe") { + ref := mload(add(chainBuffer, add(0x20, offset))) + } + } + + /// @dev Looks for the first semicolon in the chain buffer. This is the optimal way since + /// the namespace is shorter than the reference. + function _findSemicolonIndex(bytes memory chainBuffer) private pure returns (uint8) { + uint8 length = SafeCast.toUint8(chainBuffer.length); + for (uint8 i = 0; i < length; i++) { + if (chainBuffer[i] == SEMICOLON) { + return i; + } + } + return length; + } +} From 348ad8d63d0f73f2652b4e01e576285872973e3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 13 Aug 2024 19:09:11 -0600 Subject: [PATCH 08/51] Add GatewayAxelar specialization --- contracts/crosschain/Gateway.sol | 64 ++++++++++++------- contracts/crosschain/GatewayAxelar.sol | 53 +++++++++++++++ contracts/crosschain/GatewayAxelarCAIP2.sol | 33 ++++++++++ .../crosschain/ICAIP2GatewayEquivalence.sol | 13 ++++ 4 files changed, 140 insertions(+), 23 deletions(-) create mode 100644 contracts/crosschain/GatewayAxelar.sol create mode 100644 contracts/crosschain/GatewayAxelarCAIP2.sol create mode 100644 contracts/crosschain/ICAIP2GatewayEquivalence.sol diff --git a/contracts/crosschain/Gateway.sol b/contracts/crosschain/Gateway.sol index 3a5921c9..4fe6e557 100644 --- a/contracts/crosschain/Gateway.sol +++ b/contracts/crosschain/Gateway.sol @@ -41,7 +41,7 @@ abstract contract GatewaySource is IGatewaySource { string memory destination, Message memory message ) external payable virtual returns (bytes32) { - _authorizeSendingMessage(source, source, message); + _authorizeCreatingMessage(source, source, message); bytes32 id = messageId(source, destination, message); return _sendMessage(id, sendingStatus(id), message); } @@ -52,7 +52,7 @@ abstract contract GatewaySource is IGatewaySource { string memory destination, Message memory message ) external payable virtual returns (bytes32) { - _authorizeSendingMessage(source, message); + _authorizeCreatingMessage(source, message); bytes32 id = messageId(source, destination, message); return _createMessage(id, sendingStatus(id), message); } @@ -67,24 +67,13 @@ abstract contract GatewaySource is IGatewaySource { return _forwardMessage(id, sendingStatus(id), message); } - function _authorizeSendingMessage(string memory source, Message memory message) internal virtual { - CAIP10.Account memory sourceAccount = CAIP10.fromString(source); - - // Sender must match the source account - bool isSelf = Strings.equal(Strings.toHexString(msg.sender), sourceAccount._accountId); - - if (!isSelf || !_EVMValidity()) { - revert UnauthorizedSourceMessage(source, msg.sender, message); - } - } - - function _EVMValidity(CAIP10.Account memory sourceAccount) private pure { - return - sourceAccount._chainId._namespace == _chainId() && // Chain ID must match the current chain - sourceAccount._chainId._reference == bytes32(bytes(string("eip155"))); // EIP-155 for EVM chains - } - - function _createMessage(bytes32 id, MessageSourceStatus status, Message memory message) private returns (bytes32) { + function _createMessage( + bytes32 id, + string memory source, + string memory destination, + MessageSourceStatus status, + Message memory message + ) internal virtual returns (bytes32) { // Check if the message was not created or sent before. NOOP otherwise. if (status == MessageSourceStatus.Unknown) { _createdBox.insert(id); @@ -93,7 +82,13 @@ abstract contract GatewaySource is IGatewaySource { return id; } - function _sendMessage(bytes32 id, MessageSourceStatus status, Message memory message) private returns (bytes32) { + function _sendMessage( + bytes32 id, + string memory source, + string memory destination, + MessageSourceStatus status, + Message memory message + ) internal virtual returns (bytes32) { /// Check if the message hwas not sent before. NOOP otherwise. if (status != MessageSourceStatus.Sent) { _sentBox.insert(id); @@ -102,7 +97,13 @@ abstract contract GatewaySource is IGatewaySource { return id; } - function _forwardMessage(bytes32 id, MessageSourceStatus status, Message memory message) private returns (bytes32) { + function _forwardMessage( + bytes32 id, + string memory source, + string memory destination, + MessageSourceStatus status, + Message memory message + ) internal virtual returns (bytes32) { // Check if the message was created first. NOOP otherwise. if (status == MessageSourceStatus.Created) { _sendMessage(id, status, message); @@ -110,6 +111,23 @@ abstract contract GatewaySource is IGatewaySource { return id; } + function _authorizeCreatingMessage(string memory source, Message memory message) internal virtual { + CAIP10.Account memory sourceAccount = CAIP10.fromString(source); + + // Sender must match the source account + bool isSelf = Strings.equal(Strings.toHexString(msg.sender), sourceAccount._accountId); + + if (!isSelf || !_EVMValidity()) { + revert UnauthorizedSourceMessage(source, msg.sender, message); + } + } + + function _EVMValidity(CAIP10.Account memory sourceAccount) private pure { + return + sourceAccount._chainId._namespace == _chainId() && // Chain ID must match the current chain + sourceAccount._chainId._reference == bytes32(bytes(string("eip155"))); // EIP-155 for EVM chains + } + /// @dev Returns the chain ID of the current chain. /// Assumes block.chainId < type(uint64).max function _chainId() private view returns (bytes8 chainId) { @@ -187,7 +205,7 @@ abstract contract GatewayDestination is IGatewayDestination { bytes32 id, MessageDestinationStatus status, Message memory message - ) private returns (bytes32) { + ) internal virtual returns (bytes32) { // Check if the message was not delivered or executed before. NOOP otherwise. if (status == MessageDestinationStatus.Unknown) { _deliveredBox.insert(id); diff --git a/contracts/crosschain/GatewayAxelar.sol b/contracts/crosschain/GatewayAxelar.sol new file mode 100644 index 00000000..fc8fc547 --- /dev/null +++ b/contracts/crosschain/GatewayAxelar.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {IAxelarGateway} from "./vendor/axelar/interfaces/IAxelarGateway.sol"; +import {IAxelarGasService} from "./vendor/axelar/interfaces/IAxelarGasService.sol"; + +import {GatewaySource} from "./Gateway.sol"; +import {GatewayAxelarCAIP2} from "./GatewayAxelarCAIP2.sol"; +import {CAIP10} from "../utils/CAIP-10.sol"; + +contract GatewayAxelar is GatewaySource, GatewayAxelarCAIP2 { + IAxelarGateway public immutable axelarGateway; + IAxelarGasService public immutable gasService; + + error UnsupportedNativeCurrency(); + + constructor(IAxelarGateway _axelarGateway, address _initialOwner) Ownable(_initialOwner) { + axelarGateway = _axelarGateway; + } + + function _authorizeCreatingMessage( + string memory source, + Message memory message + ) internal override returns (bytes32) { + if (message.value > 0) { + revert UnsupportedNativeCurrency(); + } + + if (!isRegisteredCAIP2(CAIP10.fromString(destination)._chainId)) { + revert UnsupportedChain(chain); + } + + return super._authorizeCreatingMessage(source, message); + } + + function _sendMessage( + bytes32 id, + string memory /* source */, + string memory destination, + MessageSourceStatus status, + Message memory message + ) internal override returns (bytes32) { + super._sendMessage(id, status, message); + + if (status != MessageSourceStatus.Sent) { + AxelarChain memory details = chainDetails[CAIP10.fromString(destination)._chainId]; + gateway.callContract(details.name, details.remote, payload); + } + + return id; + } +} diff --git a/contracts/crosschain/GatewayAxelarCAIP2.sol b/contracts/crosschain/GatewayAxelarCAIP2.sol new file mode 100644 index 00000000..ccf20005 --- /dev/null +++ b/contracts/crosschain/GatewayAxelarCAIP2.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {IGatewayCAIP2Equivalence} from "./ICAIP2GatewayEquivalence.sol"; +import {CAIP2} from "../utils/CAIP-2.sol"; + +abstract contract GatewayAxelarCAIP2 is IGatewayCAIP2Equivalence { + error AlreadyRegisteredChain(CAIP2.ChainId chain); + error UnsupportedChain(CAIP2.ChainId chain); + + mapping(CAIP2.Chain chain => AxelarChain chainName) public chainDetails; + + struct AxelarChain { + string destinationChain; + string contractAddress; + } + + function isRegisteredCAIP2(CAIP2.ChainId memory chain) public view override returns (bool) { + return bytes(chainDetails[chain].destinationChain).length != 0; + } + + function fromCAIP2(CAIP2.ChainId memory chain) public pure returns (bytes memory) { + return abi.encode(AxelarChain(chain.destinationChain, chain.contractAddress)); + } + + function registerCAIP2Equivalence(CAIP2.ChainId memory chain, bytes memory custom) public onlyOwner { + if (isRegisteredCAIP2(chain)) { + revert AlreadyRegisteredChain(chain); + } + chainDetails[chain] = abi.decode(custom, (AxelarChain)); + } +} diff --git a/contracts/crosschain/ICAIP2GatewayEquivalence.sol b/contracts/crosschain/ICAIP2GatewayEquivalence.sol new file mode 100644 index 00000000..53015c93 --- /dev/null +++ b/contracts/crosschain/ICAIP2GatewayEquivalence.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {IAxelarGateway} from "./vendor/axelar/interfaces/IAxelarGateway.sol"; +import {IAxelarGasService} from "./vendor/axelar/interfaces/IAxelarGasService.sol"; +import {CAIP2} from "../utils/CAIP-2.sol"; + +interface IGatewayCAIP2Equivalence { + function registerCAIP2Equivalence(CAIP2.ChainId memory chain, bytes memory custom) public; + function isRegisteredCAIP2(CAIP2.ChainId memory chain) public view returns (bool); + function fromCAIP2(CAIP2.ChainId memory chain) public pure returns (bytes memory); +} From edfd3edc95572942c77f1b094bf55e33eeaeaf65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Wed, 14 Aug 2024 13:32:34 -0600 Subject: [PATCH 09/51] Iterate --- contracts/crosschain/Gateway.sol | 246 ------------------ contracts/crosschain/GatewayAxelar.sol | 53 ---- contracts/crosschain/GatewayDestination.sol | 106 ++++++++ contracts/crosschain/GatewaySource.sol | 145 +++++++++++ contracts/crosschain/GenericGatewayAxelar.sol | 86 ------ contracts/crosschain/GenericGatewayCommon.sol | 166 ------------ contracts/crosschain/ICAIP2Equivalence.sol | 21 ++ .../crosschain/ICAIP2GatewayEquivalence.sol | 13 - contracts/crosschain/IGateway.sol | 104 -------- contracts/crosschain/IGatewayCommon.sol | 26 ++ contracts/crosschain/IGatewayDestination.sol | 44 ++++ contracts/crosschain/IGatewaySource.sol | 52 ++++ .../{ => axelar}/GatewayAxelarCAIP2.sol | 6 +- .../crosschain/axelar/GatewayAxelarSource.sol | 63 +++++ .../generic/GatewayDestinationGeneric.sol | 58 +++++ .../generic/GatewaySourceGeneric.sol | 56 ++++ contracts/utils/CAIP-10.sol | 4 + contracts/utils/CAIP-2.sol | 24 ++ contracts/utils/LinkedList.sol | 28 ++ 19 files changed, 630 insertions(+), 671 deletions(-) delete mode 100644 contracts/crosschain/Gateway.sol delete mode 100644 contracts/crosschain/GatewayAxelar.sol create mode 100644 contracts/crosschain/GatewayDestination.sol create mode 100644 contracts/crosschain/GatewaySource.sol delete mode 100644 contracts/crosschain/GenericGatewayAxelar.sol delete mode 100644 contracts/crosschain/GenericGatewayCommon.sol create mode 100644 contracts/crosschain/ICAIP2Equivalence.sol delete mode 100644 contracts/crosschain/ICAIP2GatewayEquivalence.sol delete mode 100644 contracts/crosschain/IGateway.sol create mode 100644 contracts/crosschain/IGatewayCommon.sol create mode 100644 contracts/crosschain/IGatewayDestination.sol create mode 100644 contracts/crosschain/IGatewaySource.sol rename contracts/crosschain/{ => axelar}/GatewayAxelarCAIP2.sol (84%) create mode 100644 contracts/crosschain/axelar/GatewayAxelarSource.sol create mode 100644 contracts/crosschain/generic/GatewayDestinationGeneric.sol create mode 100644 contracts/crosschain/generic/GatewaySourceGeneric.sol create mode 100644 contracts/utils/LinkedList.sol diff --git a/contracts/crosschain/Gateway.sol b/contracts/crosschain/Gateway.sol deleted file mode 100644 index 4fe6e557..00000000 --- a/contracts/crosschain/Gateway.sol +++ /dev/null @@ -1,246 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {IGatewayDestination, IGatewaySource, IGatewayBase} from "./IGateway.sol"; -import {Set} from "../utils/Set.sol"; -import {CAIP10} from "../utils/CAIP-10.sol"; -import {CAIP2} from "../utils/CAIP-2.sol"; - -/// @dev Gateway contract on the source chain according to ERC-XXXX definitions. -/// -/// This is a generic implementation of an asynchronous message-passing system between accounts on different chains. -abstract contract GatewaySource is IGatewaySource { - using Set for Set.Bytes32Set; - using CAIP2 for CAIP2.ChainId; - using CAIP10 for CAIP10.Account; - - Set.Bytes32Set private _createdBox; - Set.Bytes32Set private _sentBox; - - /// @inheritdoc IGatewayBase - function messageId( - string memory source, - string memory destination, - Message memory message - ) public pure virtual returns (bytes32) { - return keccak256(abi.encode(source, destination, message)); - } - - /// @inheritdoc IGatewaySource - function sendingStatus(bytes32 id) public view virtual returns (MessageSourceStatus) { - if (_sentBox.contains(id)) return MessageSourceStatus.Sent; - if (_createdBox.contains(id)) return MessageSourceStatus.Created; - return MessageSourceStatus.Unknown; - } - - /// @inheritdoc IGatewaySource - function sendMessage( - string memory source, - string memory destination, - Message memory message - ) external payable virtual returns (bytes32) { - _authorizeCreatingMessage(source, source, message); - bytes32 id = messageId(source, destination, message); - return _sendMessage(id, sendingStatus(id), message); - } - - /// @inheritdoc IGatewaySource - function createMessage( - string memory source, - string memory destination, - Message memory message - ) external payable virtual returns (bytes32) { - _authorizeCreatingMessage(source, message); - bytes32 id = messageId(source, destination, message); - return _createMessage(id, sendingStatus(id), message); - } - - /// @inheritdoc IGatewaySource - function forwardMessage( - string memory source, - string memory destination, - Message memory message - ) external payable virtual returns (bytes32) { - bytes32 id = messageId(source, destination, message); - return _forwardMessage(id, sendingStatus(id), message); - } - - function _createMessage( - bytes32 id, - string memory source, - string memory destination, - MessageSourceStatus status, - Message memory message - ) internal virtual returns (bytes32) { - // Check if the message was not created or sent before. NOOP otherwise. - if (status == MessageSourceStatus.Unknown) { - _createdBox.insert(id); - emit MessageCreated(id, message); - } - return id; - } - - function _sendMessage( - bytes32 id, - string memory source, - string memory destination, - MessageSourceStatus status, - Message memory message - ) internal virtual returns (bytes32) { - /// Check if the message hwas not sent before. NOOP otherwise. - if (status != MessageSourceStatus.Sent) { - _sentBox.insert(id); - emit MessageSent(id, message); - } - return id; - } - - function _forwardMessage( - bytes32 id, - string memory source, - string memory destination, - MessageSourceStatus status, - Message memory message - ) internal virtual returns (bytes32) { - // Check if the message was created first. NOOP otherwise. - if (status == MessageSourceStatus.Created) { - _sendMessage(id, status, message); - } - return id; - } - - function _authorizeCreatingMessage(string memory source, Message memory message) internal virtual { - CAIP10.Account memory sourceAccount = CAIP10.fromString(source); - - // Sender must match the source account - bool isSelf = Strings.equal(Strings.toHexString(msg.sender), sourceAccount._accountId); - - if (!isSelf || !_EVMValidity()) { - revert UnauthorizedSourceMessage(source, msg.sender, message); - } - } - - function _EVMValidity(CAIP10.Account memory sourceAccount) private pure { - return - sourceAccount._chainId._namespace == _chainId() && // Chain ID must match the current chain - sourceAccount._chainId._reference == bytes32(bytes(string("eip155"))); // EIP-155 for EVM chains - } - - /// @dev Returns the chain ID of the current chain. - /// Assumes block.chainId < type(uint64).max - function _chainId() private view returns (bytes8 chainId) { - unchecked { - uint256 id = block.chainid; - while (true) { - chainId--; - assembly ("memory-safe") { - mstore8(chainId, byte(mod(id, 10), HEX_DIGITS)) - } - id /= 10; - if (id == 0) break; - } - } - } -} - -abstract contract GatewayDestination is IGatewayDestination { - using Set for Set.Bytes32Set; - Set.Bytes32Set private _executedBox; - Set.Bytes32Set private _deliveredBox; - - /// @inheritdoc IGatewayBase - function messageId( - string memory source, - string memory destination, - Message memory message - ) public pure virtual returns (bytes32) { - return keccak256(abi.encode(source, destination, message)); - } - - /// @inheritdoc IGatewayDestination - function destinationStatus(bytes32 id) public view virtual returns (MessageDestinationStatus) { - if (_executedBox.contains(id)) return MessageDestinationStatus.Executed; - if (_deliveredBox.contains(id)) return MessageDestinationStatus.Delivered; - return MessageDestinationStatus.Unknown; - } - - /// @inheritdoc IGatewayDestination - function deliverMessage( - string memory source, - string memory destination, - Message memory message - ) external virtual returns (bytes32) { - bytes32 id = messageId(source, destination, message); - _authorizeDeliveringMessage(destination, message); - MessageDestinationStatus status = destinationStatus(id); - return _deliverMessage(id, status, message); - } - - /// @inheritdoc IGatewayDestination - function setMessageExecuted( - string memory source, - string memory destination, - Message memory message - ) external virtual returns (bytes32) { - bytes32 id = messageId(source, destination, message); - MessageDestinationStatus status = destinationStatus(id); - return _setMessageExecuted(id, status, message); - } - - /// @dev Authorizes the delivery of a message to the destination chain. - function _authorizeDeliveringMessage(string memory destination, Message memory message) internal virtual { - CAIP10.Account memory destinationAccount = CAIP10.fromString(destination); - - if (_validateMessage(message) && !_EVMValidity(destinationAccount)) { - revert UnauthorizedDestinationMessage(destination, msg.sender, message); - } - } - - /// @dev Validates the message before delivering it. Left unimplemented to allow for custom access control. - function _validateMessage(Message memory message) internal virtual; - - function _deliverMessage( - bytes32 id, - MessageDestinationStatus status, - Message memory message - ) internal virtual returns (bytes32) { - // Check if the message was not delivered or executed before. NOOP otherwise. - if (status == MessageDestinationStatus.Unknown) { - _deliveredBox.insert(id); - emit MessageDelivered(id, message); - } - return id; - } - - function _EVMValidity(CAIP10.Account memory destinationAccount) private pure { - return - destinationAccount._chainId._namespace == _chainId() && // Chain ID must match the current chain - destinationAccount._chainId._reference == bytes32(bytes(string("eip155"))); // EIP-155 for EVM chains - } - - function _setMessageExecuted( - bytes32 id, - MessageDestinationStatus status, - Message memory message - ) private returns (bytes32) { - // Check if the message was not executed already. NOOP otherwise. - if (status != MessageDestinationStatus.Executed) { - _executedBox.insert(id); - emit MessageExecuted(id, message); - } - return id; - } -} - -/// @dev Generic implementation of a Gateway contract according to ERC-XXXX definitions. -abstract contract Gateway is GatewayDestination, GatewaySource { - function messageId( - string memory source, - string memory destination, - Message memory message - ) public pure override(GatewaySource, GatewayDestination) returns (bytes32) { - return super.messageId(source, destination, message); - } -} diff --git a/contracts/crosschain/GatewayAxelar.sol b/contracts/crosschain/GatewayAxelar.sol deleted file mode 100644 index fc8fc547..00000000 --- a/contracts/crosschain/GatewayAxelar.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {IAxelarGateway} from "./vendor/axelar/interfaces/IAxelarGateway.sol"; -import {IAxelarGasService} from "./vendor/axelar/interfaces/IAxelarGasService.sol"; - -import {GatewaySource} from "./Gateway.sol"; -import {GatewayAxelarCAIP2} from "./GatewayAxelarCAIP2.sol"; -import {CAIP10} from "../utils/CAIP-10.sol"; - -contract GatewayAxelar is GatewaySource, GatewayAxelarCAIP2 { - IAxelarGateway public immutable axelarGateway; - IAxelarGasService public immutable gasService; - - error UnsupportedNativeCurrency(); - - constructor(IAxelarGateway _axelarGateway, address _initialOwner) Ownable(_initialOwner) { - axelarGateway = _axelarGateway; - } - - function _authorizeCreatingMessage( - string memory source, - Message memory message - ) internal override returns (bytes32) { - if (message.value > 0) { - revert UnsupportedNativeCurrency(); - } - - if (!isRegisteredCAIP2(CAIP10.fromString(destination)._chainId)) { - revert UnsupportedChain(chain); - } - - return super._authorizeCreatingMessage(source, message); - } - - function _sendMessage( - bytes32 id, - string memory /* source */, - string memory destination, - MessageSourceStatus status, - Message memory message - ) internal override returns (bytes32) { - super._sendMessage(id, status, message); - - if (status != MessageSourceStatus.Sent) { - AxelarChain memory details = chainDetails[CAIP10.fromString(destination)._chainId]; - gateway.callContract(details.name, details.remote, payload); - } - - return id; - } -} diff --git a/contracts/crosschain/GatewayDestination.sol b/contracts/crosschain/GatewayDestination.sol new file mode 100644 index 00000000..778dc283 --- /dev/null +++ b/contracts/crosschain/GatewayDestination.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {CAIP10} from "../utils/CAIP-10.sol"; +import {CAIP2} from "../utils/CAIP-2.sol"; +import {IGatewayDestination, IGatewayCommon} from "./IGatewayDestination.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Set} from "../utils/Set.sol"; + +/// @dev A gateway destination that receives messages from a source chain. +/// +/// This contract allows for 2 main operations: +/// - Message delivery (i.e. making it available for execution) +/// - Message execution (i.e. marking it as executed) +/// +/// Message delivery is permissioned through {_authorizeMessageDelivered} and it's usually set +/// to an authority that can validate the message and make it available for execution. +/// +/// Message execution is permissioned through {_authorizeMessageExecuted} and it checks if the +/// destination account is the one marking the message as executed. +abstract contract GatewayDestination is IGatewayDestination { + /// @inheritdoc IGatewayCommon + function messageId( + string memory source, + string memory destination, + Message memory message + ) public pure virtual override returns (bytes32); + + /// @inheritdoc IGatewayDestination + function destinationStatus(bytes32 id) public view virtual override returns (MessageDestinationStatus); + + /// @inheritdoc IGatewayDestination + function deliverMessage( + string memory source, + string memory destination, + Message memory message + ) external returns (bytes32) { + bytes32 id = messageId(source, destination, message); + MessageDestinationStatus status = destinationStatus(id); + _validateCurrentChain(destination); + _authorizeMessageDelivered(destination, message); + return _deliverMessage(id, status, message); + } + + /// @inheritdoc IGatewayDestination + function setMessageExecuted( + string memory source, + string memory destination, + Message memory message + ) external returns (bytes32) { + bytes32 id = messageId(source, destination, message); + MessageDestinationStatus status = destinationStatus(id); + _authorizeMessageExecuted(destination, message); + return _setMessageExecuted(id, status, message); + } + + /// @dev Internal version of {deliverMessage} without access control. + function _deliverMessage( + bytes32 id, + MessageDestinationStatus status, + Message memory message + ) internal virtual returns (bytes32) { + // Check if the message was not delivered or executed before. NOOP otherwise. + if (status == MessageDestinationStatus.Unknown) { + emit MessageDelivered(id, message); + } + return id; + } + + /// @dev Internal version of {setMessageExecuted} without access control. + function _setMessageExecuted( + bytes32 id, + MessageDestinationStatus status, + Message memory message + ) internal virtual returns (bytes32) { + // Check if the message was not executed already. NOOP otherwise. + if (status != MessageDestinationStatus.Executed) { + emit MessageExecuted(id, message); + } + return id; + } + + /// @dev Authorizes the delivery of a message to the destination chain. + function _authorizeMessageDelivered(string memory destination, Message memory message) internal virtual; + + /// @dev Validates a message submitted as executed. + /// + /// Requirements: + /// - The destination must be the `msg.sender` + function _authorizeMessageExecuted(string memory destination, Message memory message) internal virtual { + CAIP10.Account memory destinationAccount = CAIP10.fromString(destination); + + if (CAIP10.getAddress(destinationAccount) != msg.sender) { + revert UnauthorizedDestinationMessage(destination, msg.sender, message); + } + } + + /// @dev Validates that the destination chain is the current chain. + function _validateCurrentChain(string memory destination) private view { + CAIP10.Account memory destinationAccount = CAIP10.fromString(destination); + if (!CAIP2.isCurrentEVMChain(destinationAccount._chainId)) { + revert MismatchedDestinationChain(destination); + } + } +} diff --git a/contracts/crosschain/GatewaySource.sol b/contracts/crosschain/GatewaySource.sol new file mode 100644 index 00000000..63dd08e7 --- /dev/null +++ b/contracts/crosschain/GatewaySource.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {CAIP10} from "../utils/CAIP-10.sol"; +import {CAIP2} from "../utils/CAIP-2.sol"; +import {IGatewaySource, IGatewayCommon} from "./IGatewaySource.sol"; +import {Set} from "../utils/Set.sol"; + +/// @dev Gateway contract on the source chain according to ERC-XXXX definitions. +/// +/// This is a generic implementation of an asynchronous message-passing system between accounts on different chains. +abstract contract GatewaySource is IGatewaySource { + /// @inheritdoc IGatewayCommon + function messageId( + string memory source, + string memory destination, + Message memory message + ) public pure virtual returns (bytes32); + + /// @inheritdoc IGatewaySource + function sendingStatus(bytes32 id) public view virtual returns (MessageSourceStatus); + + /// @inheritdoc IGatewaySource + function sendMessage( + string memory source, + string memory destination, + Message memory message + ) external payable virtual returns (bytes32) { + bytes32 id = messageId(source, destination, message); + _validateCurrentChain(source); + _authorizeMessageCreated(source, message); + return _sendMessage(id, sendingStatus(id), message); + } + + /// @inheritdoc IGatewaySource + function createMessage( + string memory source, + string memory destination, + Message memory message + ) external payable virtual returns (bytes32) { + bytes32 id = messageId(source, destination, message); + _validateCurrentChain(source); + _authorizeMessageCreated(source, message); + return _createMessage(id, sendingStatus(id), message); + } + + /// @inheritdoc IGatewaySource + function forwardMessage( + string memory source, + string memory destination, + Message memory message + ) external payable virtual returns (bytes32) { + bytes32 id = messageId(source, destination, message); + _authorizeMessageForwarded(destination, message); + return _forwardMessage(id, sendingStatus(id), message); + } + + /// @dev Internal version of {createMessage} without access control. + function _createMessage( + bytes32 id, + MessageSourceStatus status, + Message memory message + ) internal virtual returns (bytes32) { + // Check if the message was not created or sent before. NOOP otherwise. + if (status == MessageSourceStatus.Unknown) { + emit MessageCreated(id, message); + } + return id; + } + + /// @dev Internal version of {sendMessage} without access control. + function _sendMessage( + bytes32 id, + MessageSourceStatus status, + Message memory message + ) internal virtual returns (bytes32) { + /// Check if the message hwas not sent before. NOOP otherwise. + if (status != MessageSourceStatus.Sent) { + emit MessageSent(id, message); + } + return id; + } + + /// @dev Internal version of {forwardMessage} without access control. + function _forwardMessage( + bytes32 id, + MessageSourceStatus status, + Message memory message + ) internal virtual returns (bytes32) { + // Check if the message was created first. NOOP otherwise. + if (status == MessageSourceStatus.Created) { + _sendMessage(id, status, message); + } + return id; + } + + /// @dev Authorizes the creation of a message on the source chain. + /// + /// Requirements: + /// - The source chain must match `msg.sender` + function _authorizeMessageCreated(string memory source, Message memory message) internal virtual { + CAIP10.Account memory sourceAccount = CAIP10.fromString(source); + + if (CAIP10.getAddress(sourceAccount) != msg.sender) { + revert UnauthorizedSourceMessage(source, msg.sender, message); + } + } + + /// @dev Authorizes the forwarding of a message to the destination chain. + function _authorizeMessageForwarded(string memory destination, Message memory message) internal virtual; + + /// @dev Validates that the source chain is the current chain. + function _validateCurrentChain(string memory source) private view { + CAIP10.Account memory sourceAccount = CAIP10.fromString(source); + if (!CAIP2.isCurrentEVMChain(sourceAccount._chainId)) { + revert MismatchedSourceChain(source); + } + } +} + +abstract contract GatewaySourceGeneric is GatewaySource { + using Set for Set.Bytes32Set; + using CAIP10 for CAIP10.Account; + + Set.Bytes32Set private _createdBox; + Set.Bytes32Set private _sentBox; + + /// @inheritdoc IGatewayCommon + function messageId( + string memory source, + string memory destination, + Message memory message + ) public pure override returns (bytes32) { + return keccak256(abi.encode(source, destination, message)); + } + + /// @inheritdoc IGatewaySource + function sendingStatus(bytes32 id) public view virtual override returns (MessageSourceStatus) { + if (_sentBox.contains(id)) return MessageSourceStatus.Sent; + if (_createdBox.contains(id)) return MessageSourceStatus.Created; + return MessageSourceStatus.Unknown; + } +} diff --git a/contracts/crosschain/GenericGatewayAxelar.sol b/contracts/crosschain/GenericGatewayAxelar.sol deleted file mode 100644 index eebeb5fb..00000000 --- a/contracts/crosschain/GenericGatewayAxelar.sol +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {IAxelarGateway} from "./vendor/axelar/interfaces/IAxelarGateway.sol"; -import {IAxelarGasService} from "./vendor/axelar/interfaces/IAxelarGasService.sol"; - -import {GenericGatewayCommon} from "./GenericGatewayCommon.sol"; - -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {Math} from "@openzeppelin/contracts@master/utils/math/Math.sol"; - -contract GenericGatewayAxelar is GenericGatewayCommon, Ownable { - IAxelarGateway public immutable gateway; - IAxelarGasService public immutable gasService; - - struct ChainDetails { - string name; - string remote; - } - - mapping(uint256 chainId => ChainDetails chainName) public chainDetails; - - constructor(IAxelarGateway _gateway, address _initialOwner) Ownable(_initialOwner) { - gateway = _gateway; - } - - function registerForeignChainDetails(uint256 chainId, ChainDetails memory details) public onlyOwner { - require(chainId != block.chainid); - require(bytes(chainDetails[chainId].name).length == 0); - chainDetails[chainId] = details; - } - - function defaultCost(Message memory /*message*/) public pure virtual override returns (address, uint256) { - return (address(0), 0); - } - - function estimateCost(Message memory /*message*/, address asset) public pure virtual returns (uint256) { - return Math.ternary(asset == address(0), 0, type(uint256).max); - } - - /// @dev Override that the target blockchain is registered and that 0 value is passed when creating a request. - function createRequest( - uint256 chain, - address target, - bytes memory data, - bytes32 salt - ) public payable virtual override returns (bytes32) { - require(msg.value == 0, "Axelar does not support native currency bridging"); - - // retrieve chain details for the destination chain - ChainDetails storage details = chainDetails[chain]; - require(bytes(details.name).length > 0, "Remote chain not registered"); - - return super.createRequest(chain, target, data, salt); - } - - function _processRequest( - bytes32 /*id*/, - Request memory req, - address feeAsset, - uint256 feeValue - ) internal virtual override { - require(feeAsset == address(0), "Axelar only supports fees in native currency"); - require(req.message.value == 0, "Axelar does not support native currency bridging"); - - ChainDetails storage details = chainDetails[req.message.destination.chain]; - require(bytes(details.name).length > 0, "Remote chain not registered"); - - bytes memory payload = abi.encode(req); - - // If value is provided, forward it to the gasService - if (feeValue > 0) { - gasService.payNativeGasForContractCall{value: feeValue}( - address(this), - details.name, - details.remote, - payload, - msg.sender - ); - } - - // send cross-chain signal - gateway.callContract(details.name, details.remote, payload); - } -} diff --git a/contracts/crosschain/GenericGatewayCommon.sol b/contracts/crosschain/GenericGatewayCommon.sol deleted file mode 100644 index b0ebd145..00000000 --- a/contracts/crosschain/GenericGatewayCommon.sol +++ /dev/null @@ -1,166 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {IGenericGateway} from "./IGenericGateway.sol"; -import {Set} from "../utils/Set.sol"; - -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; -import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {SlotDerivation} from "@openzeppelin/contracts@master/utils/SlotDerivation.sol"; -import {StorageSlot} from "@openzeppelin/contracts@master/utils/StorageSlot.sol"; - -/** - * @dev Generic implementation of a Gateway contract according to ERC-XXXX definitions. - */ -abstract contract GenericGatewayCommon is IGenericGateway { - using SlotDerivation for *; - using StorageSlot for *; - using Set for Set.Bytes32Set; - - event RequestCreated(bytes32 id, Request req); - event RequestForwarded(bytes32 id); - event RequestExecuted(bytes32 id); - - Set.Bytes32Set private _outBox; - - // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.GenericGatewayCommon")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant GENERIC_GATEWAY_COMMON_STORAGE = - 0x71ab59e9fe1edd5f3d56389a2c715a90ddbb606dacc41e1c5360c10e3fe15b00; - - function crossChainSender() public view returns (uint256 chainId, address sender) { - return ( - GENERIC_GATEWAY_COMMON_STORAGE.offset(0).asUint256().tload(), - GENERIC_GATEWAY_COMMON_STORAGE.offset(1).asAddress().tload() - ); - } - - // This must be redeclared as public so that other function can call it - function defaultCost(Message memory /*message*/) public pure virtual returns (address, uint256); - - function sendRequest( - uint256 chain, - address target, - uint256 value, - bytes memory data, - bytes32 salt - ) public payable virtual returns (bytes32) { - (address feeAsset, uint256 feeValue) = defaultCost( - Message({destination: Account({chain: chain, instance: target}), value: value, data: data}) - ); - return sendRequest(chain, target, value, data, salt, feeAsset, feeValue); - } - - function sendRequest( - uint256 chain, - address target, - uint256 value, - bytes memory data, - bytes32 salt, - address feeAsset, - uint256 feeValue - ) public payable virtual returns (bytes32) { - // build request, payload and hash - Request memory req = _generateRequest(chain, target, msg.sender, value, data, salt); - bytes32 id = keccak256(abi.encode(req)); - - if (feeAsset == address(0)) { - uint256 totalValue = value + feeValue; - require(msg.value >= totalValue, "invalid value provided"); - if (msg.value > totalValue) Address.sendValue(payable(msg.sender), msg.value - totalValue); - } else { - require(msg.value >= value, "invalid value provided"); - if (feeValue > 0) SafeERC20.safeTransferFrom(IERC20(feeAsset), msg.sender, address(this), feeValue); - if (msg.value > value) Address.sendValue(payable(msg.sender), msg.value - value); - } - - _processRequest(id, req, feeAsset, feeValue); - - // TODO: event - - return id; - } - - // ================================================= 2 step mode ================================================= - - function createRequest( - uint256 chain, - address target, - bytes memory data, - bytes32 salt - ) public payable virtual returns (bytes32) { - // build request, payload and hash - Request memory req = _generateRequest(chain, target, msg.sender, msg.value, data, salt); - bytes32 id = keccak256(abi.encode(req)); - - // register the request hash - require(_outBox.insert(id), "Ticket already scheduled"); - - // emit notice - emit RequestCreated(id, req); - - return id; - } - - function forwardRequest(Request memory req) public payable virtual { - (address feeAsset, uint256 feeValue) = defaultCost(req.message); - forwardRequest(req, feeAsset, feeValue); - } - - function forwardRequest(Request memory req, address feeAsset, uint256 feeValue) public payable virtual { - // compute the request hash - bytes32 id = keccak256(abi.encode(req)); - - if (feeAsset == address(0)) { - require(msg.value >= feeValue, "invalid value provided"); - if (msg.value > feeValue) Address.sendValue(payable(msg.sender), msg.value - feeValue); - } else { - if (feeValue > 0) SafeERC20.safeTransferFrom(IERC20(feeAsset), msg.sender, address(this), feeValue); - if (msg.value > 0) Address.sendValue(payable(msg.sender), msg.value); - } - - // consume request hash - require(_outBox.remove(id), "Ticket not scheduled"); - - _processRequest(id, req, feeAsset, feeValue); - - // emit notice - emit RequestForwarded(id); - } - - // =============================================== specialisation ================================================ - - function _processRequest(bytes32 id, Request memory req, address feeAsset, uint256 feeValue) internal virtual; - - // =================================================== helpers =================================================== - function _generateRequest( - uint256 chain, - address target, - address sender, - uint256 value, - bytes memory data, - bytes32 salt - ) internal view returns (Request memory) { - return - Request({ - source: Account({chain: block.chainid, instance: sender}), - message: Message({destination: Account({chain: chain, instance: target}), value: value, data: data}), - salt: salt - }); - } - - function _executeRequest(Request memory req) internal { - require(req.message.destination.chain == block.chainid); - - GENERIC_GATEWAY_COMMON_STORAGE.offset(0).asUint256().tstore(req.source.chain); - GENERIC_GATEWAY_COMMON_STORAGE.offset(1).asAddress().tstore(req.source.instance); - - (bool success, bytes memory returndata) = req.message.destination.instance.call{value: req.message.value}( - req.message.data - ); - Address.verifyCallResult(success, returndata); - - GENERIC_GATEWAY_COMMON_STORAGE.offset(0).asUint256().tstore(0); - GENERIC_GATEWAY_COMMON_STORAGE.offset(1).asAddress().tstore(address(0)); - } -} diff --git a/contracts/crosschain/ICAIP2Equivalence.sol b/contracts/crosschain/ICAIP2Equivalence.sol new file mode 100644 index 00000000..11e696e5 --- /dev/null +++ b/contracts/crosschain/ICAIP2Equivalence.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {IAxelarGateway} from "./vendor/axelar/interfaces/IAxelarGateway.sol"; +import {IAxelarGasService} from "./vendor/axelar/interfaces/IAxelarGasService.sol"; +import {CAIP2} from "../utils/CAIP-2.sol"; + +/// @dev Equivalence interface between CAIP-2 chain identifiers and Gateway-specific chain identifiers. +/// +/// See https://chainagnostic.org/CAIPs/caip-2[CAIP2]. +interface ICAIP2Equivalence { + /// @dev Sets a CAIP-2 chain identifier as equivalent to a Gateway-specific chain identifier. + function setCAIP2Equivalence(CAIP2.ChainId memory chain, bytes memory custom) external; + + /// @dev Checks if a CAIP-2 chain identifier is registered as equivalent to a Gateway-specific chain identifier. + function exists(CAIP2.ChainId memory chain) external view returns (bool); + + /// @dev Retrieves the Gateway-specific chain identifier equivalent to a CAIP-2 chain identifier. + function fromCAIP2(CAIP2.ChainId memory chain) external pure returns (bytes memory); +} diff --git a/contracts/crosschain/ICAIP2GatewayEquivalence.sol b/contracts/crosschain/ICAIP2GatewayEquivalence.sol deleted file mode 100644 index 53015c93..00000000 --- a/contracts/crosschain/ICAIP2GatewayEquivalence.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {IAxelarGateway} from "./vendor/axelar/interfaces/IAxelarGateway.sol"; -import {IAxelarGasService} from "./vendor/axelar/interfaces/IAxelarGasService.sol"; -import {CAIP2} from "../utils/CAIP-2.sol"; - -interface IGatewayCAIP2Equivalence { - function registerCAIP2Equivalence(CAIP2.ChainId memory chain, bytes memory custom) public; - function isRegisteredCAIP2(CAIP2.ChainId memory chain) public view returns (bool); - function fromCAIP2(CAIP2.ChainId memory chain) public pure returns (bytes memory); -} diff --git a/contracts/crosschain/IGateway.sol b/contracts/crosschain/IGateway.sol deleted file mode 100644 index 3fb6cadd..00000000 --- a/contracts/crosschain/IGateway.sol +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -/// @dev Interface for a generic cross-chain gateway. -/// The gateway is responsible for sending and receiving messages between different blockchains. -interface IGatewayBase { - /// @dev Represents a cross-chain message. - struct Message { - // Native token value to be sent with the message. - uint256 value; - // Arbitrary data to be sent with the message. - bytes payload; - // Extra parameters to be used by the gateway specialization. - bytes extraParams; - } - - /// @dev Uniquely identifies a message. - /// @param source CAIP-10 account ID of the source chain. - /// @param destination CAIP-10 account ID of the destination chain. - function messageId( - string memory source, - string memory destination, - Message memory message - ) external pure returns (bytes32); -} - -/// @dev Interface for a cross-chain gateway that sends messages. -/// Allows for 2 sending modes: {sendMessage} and {createMessage} + {forwardMessage}, -/// where the latter allows to hook logic before sending the message. -interface IGatewaySource is IGatewayBase { - enum MessageSourceStatus { - Unknown, - Created, - Sent - } - - event MessageCreated(bytes32 indexed id, Message message); - event MessageSent(bytes32 indexed id, Message message); - - error UnauthorizedSourceMessage(string source, address sender, Message message); - - /// @dev Returns the status of a sent cross-chain message. - function sendingStatus(bytes32 id) external view returns (MessageSourceStatus); - - /// @dev Send a cross-chain message to the target chain. - /// MessageSourceStatus.Unknown -> MessageSourceStatus.Sent - function sendMessage( - string memory source, - string memory destination, - Message memory message - ) external payable returns (bytes32); - - /// @dev Create a cross-chain message to the target chain. See {forwardMessage} to send it. - /// MessageSourceStatus.Unknown -> MessageSourceStatus.Created - function createMessage( - string memory source, - string memory destination, - Message memory message - ) external payable returns (bytes32); - - /// @dev Forwards a previously created cross-chain message to the target chain. See {createMessage} to create it. - /// MessageSourceStatus.Created -> MessageSourceStatus.Sent - function forwardMessage( - string memory source, - string memory destination, - Message memory message - ) external payable returns (bytes32); -} - -/// @dev Interface for a cross-chain gateway that receives messages. -/// Allows to check the status of a message and to mark it as delivered or executed. -interface IGatewayDestination is IGatewayBase { - enum MessageDestinationStatus { - Unknown, - Delivered, - Executed - } - - event MessageDelivered(bytes32 indexed id, Message message); - event MessageExecuted(bytes32 indexed id, Message message); - - /// @dev Returns the status of a received cross-chain message. - function destinationStatus(bytes32 id) external view returns (MessageDestinationStatus); - - /// @dev Sets a cross-chain message as delivered, ready for execution. - /// MessageDestinationStatus.Unknown -> MessageDestinationStatus.Delivered - /// NOTE: Should only be called by an authorized gateway operator. - function deliverMessage( - string memory source, - string memory destination, - Message memory message - ) external returns (bytes32); - - /// @dev Marks a cross-chain message as executed. - /// MessageDestinationStatus.Unknown -> MessageDestinationStatus.Executed. - /// MessageDestinationStatus.Delivered -> MessageDestinationStatus.Executed. - /// NOTE: Should only be called by the destination account. - function setMessageExecuted( - string memory source, - string memory destination, - Message memory message - ) external returns (bytes32); -} diff --git a/contracts/crosschain/IGatewayCommon.sol b/contracts/crosschain/IGatewayCommon.sol new file mode 100644 index 00000000..2d198ad3 --- /dev/null +++ b/contracts/crosschain/IGatewayCommon.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/// @dev Common for a generic cross-chain gateway. +/// +/// Gateways are split in two parts: source and destination. Respectively, they are responsible for +/// sending and receiving messages between different blockchains. +interface IGatewayCommon { + /// @dev Represents a cross-chain message. + struct Message { + // Arbitrary data to be sent with the message. + bytes payload; + // Extra parameters to be used by the gateway specialization. + bytes extraParams; + } + + /// @dev Uniquely identifies a message. + /// @param source CAIP-10 account ID of the source chain. + /// @param destination CAIP-10 account ID of the destination chain. + function messageId( + string memory source, + string memory destination, + Message memory message + ) external pure returns (bytes32); +} diff --git a/contracts/crosschain/IGatewayDestination.sol b/contracts/crosschain/IGatewayDestination.sol new file mode 100644 index 00000000..02224cd9 --- /dev/null +++ b/contracts/crosschain/IGatewayDestination.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {IGatewayCommon} from "./IGatewayCommon.sol"; + +/// @dev Interface for a cross-chain gateway that receives messages. +/// +/// Allows to check the status of a message and to mark it as delivered or executed. +interface IGatewayDestination is IGatewayCommon { + enum MessageDestinationStatus { + Unknown, + Delivered, + Executed + } + + event MessageDelivered(bytes32 indexed id, Message message); + event MessageExecuted(bytes32 indexed id, Message message); + + error UnauthorizedDestinationMessage(string destination, address sender, Message message); + error MismatchedDestinationChain(string destination); + + /// @dev Returns the status of a received cross-chain message. + function destinationStatus(bytes32 id) external view returns (MessageDestinationStatus); + + /// @dev Sets a cross-chain message as delivered, ready for execution. + /// MessageDestinationStatus.Unknown -> MessageDestinationStatus.Delivered + /// NOTE: Should only be called by an authorized gateway operator and validate that destination is the current chain. + function deliverMessage( + string memory source, + string memory destination, + Message memory message + ) external returns (bytes32); + + /// @dev Sets a cross-chain message as executed. + /// MessageDestinationStatus.Unknown -> MessageDestinationStatus.Executed. + /// MessageDestinationStatus.Delivered -> MessageDestinationStatus.Executed. + /// NOTE: Should only be called by the destination account. + function setMessageExecuted( + string memory source, + string memory destination, + Message memory message + ) external returns (bytes32); +} diff --git a/contracts/crosschain/IGatewaySource.sol b/contracts/crosschain/IGatewaySource.sol new file mode 100644 index 00000000..ba4c12b1 --- /dev/null +++ b/contracts/crosschain/IGatewaySource.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {IGatewayCommon} from "./IGatewayCommon.sol"; + +/// @dev Interface for a cross-chain gateway that sends messages. +/// +/// Allows for 2 sending modes: {sendMessage} and {createMessage} + {forwardMessage}, +/// where the latter allows to hook logic before sending the message. +interface IGatewaySource is IGatewayCommon { + enum MessageSourceStatus { + Unknown, + Created, + Sent + } + + event MessageCreated(bytes32 indexed id, Message message); + event MessageSent(bytes32 indexed id, Message message); + + error UnauthorizedSourceMessage(string source, address sender, Message message); + error MismatchedSourceChain(string source); + + /// @dev Returns the status of a sent cross-chain message. + function sendingStatus(bytes32 id) external view returns (MessageSourceStatus); + + /// @dev Send a cross-chain message to the target chain. + /// MessageSourceStatus.Unknown -> MessageSourceStatus.Sent + /// NOTE: Must validate that source is the current chain. + function sendMessage( + string memory source, + string memory destination, + Message memory message + ) external payable returns (bytes32); + + /// @dev Create a cross-chain message to the target chain. See {forwardMessage} to send it. + /// MessageSourceStatus.Unknown -> MessageSourceStatus.Created + /// NOTE: Must validate that source is the current chain. + function createMessage( + string memory source, + string memory destination, + Message memory message + ) external payable returns (bytes32); + + /// @dev Forwards a previously created cross-chain message to the target chain. See {createMessage} to create it. + /// MessageSourceStatus.Created -> MessageSourceStatus.Sent + function forwardMessage( + string memory source, + string memory destination, + Message memory message + ) external payable returns (bytes32); +} diff --git a/contracts/crosschain/GatewayAxelarCAIP2.sol b/contracts/crosschain/axelar/GatewayAxelarCAIP2.sol similarity index 84% rename from contracts/crosschain/GatewayAxelarCAIP2.sol rename to contracts/crosschain/axelar/GatewayAxelarCAIP2.sol index ccf20005..6fa778fb 100644 --- a/contracts/crosschain/GatewayAxelarCAIP2.sol +++ b/contracts/crosschain/axelar/GatewayAxelarCAIP2.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.0; -import {IGatewayCAIP2Equivalence} from "./ICAIP2GatewayEquivalence.sol"; -import {CAIP2} from "../utils/CAIP-2.sol"; +import {ICAIP2Equivalence} from "../ICAIP2Equivalence.sol"; +import {CAIP2} from "../../utils/CAIP-2.sol"; -abstract contract GatewayAxelarCAIP2 is IGatewayCAIP2Equivalence { +abstract contract GatewayAxelarCAIP2 is ICAIP2Equivalence { error AlreadyRegisteredChain(CAIP2.ChainId chain); error UnsupportedChain(CAIP2.ChainId chain); diff --git a/contracts/crosschain/axelar/GatewayAxelarSource.sol b/contracts/crosschain/axelar/GatewayAxelarSource.sol new file mode 100644 index 00000000..3136e706 --- /dev/null +++ b/contracts/crosschain/axelar/GatewayAxelarSource.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {IAxelarGateway} from "../vendor/axelar/interfaces/IAxelarGateway.sol"; +import {IAxelarGasService} from "../vendor/axelar/interfaces/IAxelarGasService.sol"; + +import {GatewaySource} from "../GatewaySource.sol"; +import {GatewayAxelarCAIP2} from "./GatewayAxelarCAIP2.sol"; +import {CAIP10} from "../../utils/CAIP-10.sol"; + +contract GatewayAxelarSource is GatewaySource, GatewayAxelarCAIP2 { + IAxelarGateway public immutable axelarGateway; + IAxelarGasService public immutable gasService; + + constructor(IAxelarGateway _axelarGateway, address _initialOwner) Ownable(_initialOwner) { + axelarGateway = _axelarGateway; + } + + /// @inheritdoc GatewayDestination + function messageId( + string memory source, + string memory destination, + Message memory message + ) public pure override returns (bytes32) { + return keccak256(abi.encode(source, destination, message)); + } + + /// @inheritdoc GatewayDestination + function destinationStatus(bytes32 id) public view override returns (MessageDestinationStatus) { + if (axelarGateway.isCommandExecuted(commandId)) return MessageDestinationStatus.Executed; + if (_deliveredBox.contains(id)) return MessageDestinationStatus.Delivered; + return MessageDestinationStatus.Unknown; + } + + function _authorizeMessageCreated( + string memory source, + Message memory message + ) internal override returns (bytes32) { + if (!isRegisteredCAIP2(CAIP10.fromString(destination)._chainId)) { + revert UnsupportedChain(chain); + } + + return super._authorizeMessageCreated(source, message); + } + + function _sendMessage( + bytes32 id, + string memory /* source */, + string memory destination, + MessageSourceStatus status, + Message memory message + ) internal override returns (bytes32) { + super._sendMessage(id, status, message); + + if (status != MessageSourceStatus.Sent) { + AxelarChain memory details = chainDetails[CAIP10.fromString(destination)._chainId]; + axelarGateway.callContract(details.name, details.remote, payload); + } + + return id; + } +} diff --git a/contracts/crosschain/generic/GatewayDestinationGeneric.sol b/contracts/crosschain/generic/GatewayDestinationGeneric.sol new file mode 100644 index 00000000..9e39cc85 --- /dev/null +++ b/contracts/crosschain/generic/GatewayDestinationGeneric.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {CAIP10} from "../../utils/CAIP-10.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {GatewayDestination} from "../GatewayDestination.sol"; +import {Set} from "../../utils/Set.sol"; + +contract GatewayDestinationGeneric is GatewayDestination, Ownable { + using Set for Set.Bytes32Set; + Set.Bytes32Set private _executedBox; + Set.Bytes32Set private _deliveredBox; + + constructor() Ownable(msg.sender) {} + + /// @inheritdoc GatewayDestination + function messageId( + string memory source, + string memory destination, + Message memory message + ) public pure override returns (bytes32) { + return keccak256(abi.encode(source, destination, message)); + } + + /// @inheritdoc GatewayDestination + function destinationStatus(bytes32 id) public view override returns (MessageDestinationStatus) { + if (_executedBox.contains(id)) return MessageDestinationStatus.Executed; + if (_deliveredBox.contains(id)) return MessageDestinationStatus.Delivered; + return MessageDestinationStatus.Unknown; + } + + /// @inheritdoc GatewayDestination + function _authorizeMessageDelivered( + string memory destination, + Message memory message + ) internal virtual override onlyOwner {} + + /// @inheritdoc GatewayDestination + function _deliverMessage( + bytes32 id, + MessageDestinationStatus, + Message memory + ) internal virtual override returns (bytes32) { + _deliveredBox.insert(id); // Idempotent + return id; + } + + /// @inheritdoc GatewayDestination + function _setMessageExecuted( + bytes32 id, + MessageDestinationStatus, + Message memory + ) internal virtual override returns (bytes32) { + _executedBox.insert(id); // Idempotent + return id; + } +} diff --git a/contracts/crosschain/generic/GatewaySourceGeneric.sol b/contracts/crosschain/generic/GatewaySourceGeneric.sol new file mode 100644 index 00000000..49bcaae2 --- /dev/null +++ b/contracts/crosschain/generic/GatewaySourceGeneric.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {CAIP10} from "../../utils/CAIP-10.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {GatewaySource} from "../GatewaySource.sol"; +import {Set} from "../../utils/Set.sol"; + +contract GatewaySourceGeneric is GatewaySource, Ownable { + using Set for Set.Bytes32Set; + using CAIP10 for CAIP10.Account; + + Set.Bytes32Set private _createdBox; + Set.Bytes32Set private _sentBox; + + constructor() Ownable(msg.sender) {} + + /// @inheritdoc GatewaySource + function messageId( + string memory source, + string memory destination, + Message memory message + ) public pure override returns (bytes32) { + return keccak256(abi.encode(source, destination, message)); + } + + /// @inheritdoc GatewaySource + function sendingStatus(bytes32 id) public view virtual override returns (MessageSourceStatus) { + if (_sentBox.contains(id)) return MessageSourceStatus.Sent; + if (_createdBox.contains(id)) return MessageSourceStatus.Created; + return MessageSourceStatus.Unknown; + } + + /// @inheritdoc GatewaySource + function _authorizeMessageForwarded( + string memory destination, + Message memory message + ) internal virtual override onlyOwner {} + + /// @inheritdoc GatewaySource + function _createMessage( + bytes32 id, + MessageSourceStatus, + Message memory + ) internal virtual override returns (bytes32) { + _createdBox.insert(id); + return id; + } + + /// @inheritdoc GatewaySource + function _sendMessage(bytes32 id, MessageSourceStatus, Message memory) internal virtual override returns (bytes32) { + _sentBox.insert(id); + return id; + } +} diff --git a/contracts/utils/CAIP-10.sol b/contracts/utils/CAIP-10.sol index 687d54cf..a9c26c2f 100644 --- a/contracts/utils/CAIP-10.sol +++ b/contracts/utils/CAIP-10.sol @@ -51,6 +51,10 @@ library CAIP10 { return string(_accountId); } + function getAddress(Account memory account) internal pure returns (address) { + return address(bytes20(bytes(account._accountId))); + } + function _findLastSeparatorIndex(bytes memory accountBuffer) private pure returns (uint256) { for (uint256 i = accountBuffer.length - 1; i >= 0; i--) { if (accountBuffer[i] == CAIP2.SEMICOLON) { diff --git a/contracts/utils/CAIP-2.sol b/contracts/utils/CAIP-2.sol index 61e5faee..9a7ae9a8 100644 --- a/contracts/utils/CAIP-2.sol +++ b/contracts/utils/CAIP-2.sol @@ -6,6 +6,8 @@ import {Arrays} from "@openzeppelin/contracts/utils/Arrays.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; library CAIP2 { + bytes16 private constant HEX_DIGITS = "0123456789abcdef"; + // chain_id: namespace + ":" + reference // namespace: [-a-z0-9]{3,8} // reference: [-_a-zA-Z0-9]{1,32} @@ -61,4 +63,26 @@ library CAIP2 { } return length; } + + function isCurrentEVMChain(ChainId memory chain) internal view returns (bool) { + return + chain._namespace == currentChainId() && // Chain ID must match the current chain + chain._reference == bytes32(bytes(string("eip155"))); // EIP-155 for EVM chains + } + + /// @dev Returns the chain ID of the current chain. + /// Assumes block.chainId < type(uint64).max + function currentChainId() internal view returns (bytes8 _chainId) { + unchecked { + uint256 id = block.chainid; + while (true) { + _chainId = bytes8(uint64(_chainId) - 1); + assembly ("memory-safe") { + mstore8(_chainId, byte(mod(id, 10), HEX_DIGITS)) + } + id /= 10; + if (id == 0) break; + } + } + } } diff --git a/contracts/utils/LinkedList.sol b/contracts/utils/LinkedList.sol new file mode 100644 index 00000000..fe2d2932 --- /dev/null +++ b/contracts/utils/LinkedList.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {Panic} from "@openzeppelin/contracts@master/utils/Panic.sol"; + +/** + * @dev A linked list implemented in an array. + */ +library LinkedList { + struct Node { + uint256 next; + uint256 prev; + bytes32 _value; + } + + struct List { + Node[] nodes; + } + + function insert(Node[] storage self, uint256 index, uint256 value) internal { + if (index > self.length) revert Panic.panic(Panic.ARRAY_OUT_OF_BOUNDS); + + self.push(self[index]); + self[index].prev = self.length - 1; + self[index].next = value; + } +} From 645794b2f74b12bae20fc14c3fef96c6e87682ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 20 Aug 2024 09:54:06 -0600 Subject: [PATCH 10/51] Fix GatewayAxelarSource --- contracts/crosschain/axelar/GatewayAxelarSource.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/crosschain/axelar/GatewayAxelarSource.sol b/contracts/crosschain/axelar/GatewayAxelarSource.sol index 3136e706..b5347669 100644 --- a/contracts/crosschain/axelar/GatewayAxelarSource.sol +++ b/contracts/crosschain/axelar/GatewayAxelarSource.sol @@ -55,7 +55,7 @@ contract GatewayAxelarSource is GatewaySource, GatewayAxelarCAIP2 { if (status != MessageSourceStatus.Sent) { AxelarChain memory details = chainDetails[CAIP10.fromString(destination)._chainId]; - axelarGateway.callContract(details.name, details.remote, payload); + axelarGateway.callContract(details.destinationChain, details.contractAddress, payload); } return id; From 270c5bdbb821e5d40c1d73021fb35c125d45414b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 26 Aug 2024 11:25:38 -0600 Subject: [PATCH 11/51] Remove unnecessary contract --- contracts/utils/LinkedList.sol | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 contracts/utils/LinkedList.sol diff --git a/contracts/utils/LinkedList.sol b/contracts/utils/LinkedList.sol deleted file mode 100644 index fe2d2932..00000000 --- a/contracts/utils/LinkedList.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {Panic} from "@openzeppelin/contracts@master/utils/Panic.sol"; - -/** - * @dev A linked list implemented in an array. - */ -library LinkedList { - struct Node { - uint256 next; - uint256 prev; - bytes32 _value; - } - - struct List { - Node[] nodes; - } - - function insert(Node[] storage self, uint256 index, uint256 value) internal { - if (index > self.length) revert Panic.panic(Panic.ARRAY_OUT_OF_BOUNDS); - - self.push(self[index]); - self[index].prev = self.length - 1; - self[index].next = value; - } -} From 4605a57ab97231542d0b97ebca5cec26618d5ad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 26 Aug 2024 14:08:57 -0600 Subject: [PATCH 12/51] Iteration --- contracts/crosschain/GatewayDestination.sol | 106 ------------- contracts/crosschain/GatewaySource.sol | 145 ------------------ contracts/crosschain/ICAIP2Equivalence.sol | 14 +- contracts/crosschain/IGatewayCommon.sol | 26 ---- contracts/crosschain/IGatewayDestination.sol | 44 ------ contracts/crosschain/IGatewayIncoming.sol | 17 ++ .../crosschain/IGatewayIncomingPassive.sol | 17 ++ contracts/crosschain/IGatewayOutgoing.sol | 22 +++ contracts/crosschain/IGatewayReceiver.sol | 13 ++ contracts/crosschain/IGatewaySource.sol | 52 ------- .../axelar/AxelarCAIP2Equivalence.sol | 88 +++++++++++ .../axelar/AxelarGatewayOutgoing.sol | 26 ++++ .../crosschain/axelar/GatewayAxelarCAIP2.sol | 33 ---- .../crosschain/axelar/GatewayAxelarSource.sol | 63 -------- .../generic/GatewayDestinationGeneric.sol | 58 ------- .../generic/GatewaySourceGeneric.sol | 56 ------- contracts/utils/CAIP-10.sol | 41 ++--- contracts/utils/CAIP-2.sol | 69 ++++----- 18 files changed, 233 insertions(+), 657 deletions(-) delete mode 100644 contracts/crosschain/GatewayDestination.sol delete mode 100644 contracts/crosschain/GatewaySource.sol delete mode 100644 contracts/crosschain/IGatewayCommon.sol delete mode 100644 contracts/crosschain/IGatewayDestination.sol create mode 100644 contracts/crosschain/IGatewayIncoming.sol create mode 100644 contracts/crosschain/IGatewayIncomingPassive.sol create mode 100644 contracts/crosschain/IGatewayOutgoing.sol create mode 100644 contracts/crosschain/IGatewayReceiver.sol delete mode 100644 contracts/crosschain/IGatewaySource.sol create mode 100644 contracts/crosschain/axelar/AxelarCAIP2Equivalence.sol create mode 100644 contracts/crosschain/axelar/AxelarGatewayOutgoing.sol delete mode 100644 contracts/crosschain/axelar/GatewayAxelarCAIP2.sol delete mode 100644 contracts/crosschain/axelar/GatewayAxelarSource.sol delete mode 100644 contracts/crosschain/generic/GatewayDestinationGeneric.sol delete mode 100644 contracts/crosschain/generic/GatewaySourceGeneric.sol diff --git a/contracts/crosschain/GatewayDestination.sol b/contracts/crosschain/GatewayDestination.sol deleted file mode 100644 index 778dc283..00000000 --- a/contracts/crosschain/GatewayDestination.sol +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {CAIP10} from "../utils/CAIP-10.sol"; -import {CAIP2} from "../utils/CAIP-2.sol"; -import {IGatewayDestination, IGatewayCommon} from "./IGatewayDestination.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {Set} from "../utils/Set.sol"; - -/// @dev A gateway destination that receives messages from a source chain. -/// -/// This contract allows for 2 main operations: -/// - Message delivery (i.e. making it available for execution) -/// - Message execution (i.e. marking it as executed) -/// -/// Message delivery is permissioned through {_authorizeMessageDelivered} and it's usually set -/// to an authority that can validate the message and make it available for execution. -/// -/// Message execution is permissioned through {_authorizeMessageExecuted} and it checks if the -/// destination account is the one marking the message as executed. -abstract contract GatewayDestination is IGatewayDestination { - /// @inheritdoc IGatewayCommon - function messageId( - string memory source, - string memory destination, - Message memory message - ) public pure virtual override returns (bytes32); - - /// @inheritdoc IGatewayDestination - function destinationStatus(bytes32 id) public view virtual override returns (MessageDestinationStatus); - - /// @inheritdoc IGatewayDestination - function deliverMessage( - string memory source, - string memory destination, - Message memory message - ) external returns (bytes32) { - bytes32 id = messageId(source, destination, message); - MessageDestinationStatus status = destinationStatus(id); - _validateCurrentChain(destination); - _authorizeMessageDelivered(destination, message); - return _deliverMessage(id, status, message); - } - - /// @inheritdoc IGatewayDestination - function setMessageExecuted( - string memory source, - string memory destination, - Message memory message - ) external returns (bytes32) { - bytes32 id = messageId(source, destination, message); - MessageDestinationStatus status = destinationStatus(id); - _authorizeMessageExecuted(destination, message); - return _setMessageExecuted(id, status, message); - } - - /// @dev Internal version of {deliverMessage} without access control. - function _deliverMessage( - bytes32 id, - MessageDestinationStatus status, - Message memory message - ) internal virtual returns (bytes32) { - // Check if the message was not delivered or executed before. NOOP otherwise. - if (status == MessageDestinationStatus.Unknown) { - emit MessageDelivered(id, message); - } - return id; - } - - /// @dev Internal version of {setMessageExecuted} without access control. - function _setMessageExecuted( - bytes32 id, - MessageDestinationStatus status, - Message memory message - ) internal virtual returns (bytes32) { - // Check if the message was not executed already. NOOP otherwise. - if (status != MessageDestinationStatus.Executed) { - emit MessageExecuted(id, message); - } - return id; - } - - /// @dev Authorizes the delivery of a message to the destination chain. - function _authorizeMessageDelivered(string memory destination, Message memory message) internal virtual; - - /// @dev Validates a message submitted as executed. - /// - /// Requirements: - /// - The destination must be the `msg.sender` - function _authorizeMessageExecuted(string memory destination, Message memory message) internal virtual { - CAIP10.Account memory destinationAccount = CAIP10.fromString(destination); - - if (CAIP10.getAddress(destinationAccount) != msg.sender) { - revert UnauthorizedDestinationMessage(destination, msg.sender, message); - } - } - - /// @dev Validates that the destination chain is the current chain. - function _validateCurrentChain(string memory destination) private view { - CAIP10.Account memory destinationAccount = CAIP10.fromString(destination); - if (!CAIP2.isCurrentEVMChain(destinationAccount._chainId)) { - revert MismatchedDestinationChain(destination); - } - } -} diff --git a/contracts/crosschain/GatewaySource.sol b/contracts/crosschain/GatewaySource.sol deleted file mode 100644 index 63dd08e7..00000000 --- a/contracts/crosschain/GatewaySource.sol +++ /dev/null @@ -1,145 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {CAIP10} from "../utils/CAIP-10.sol"; -import {CAIP2} from "../utils/CAIP-2.sol"; -import {IGatewaySource, IGatewayCommon} from "./IGatewaySource.sol"; -import {Set} from "../utils/Set.sol"; - -/// @dev Gateway contract on the source chain according to ERC-XXXX definitions. -/// -/// This is a generic implementation of an asynchronous message-passing system between accounts on different chains. -abstract contract GatewaySource is IGatewaySource { - /// @inheritdoc IGatewayCommon - function messageId( - string memory source, - string memory destination, - Message memory message - ) public pure virtual returns (bytes32); - - /// @inheritdoc IGatewaySource - function sendingStatus(bytes32 id) public view virtual returns (MessageSourceStatus); - - /// @inheritdoc IGatewaySource - function sendMessage( - string memory source, - string memory destination, - Message memory message - ) external payable virtual returns (bytes32) { - bytes32 id = messageId(source, destination, message); - _validateCurrentChain(source); - _authorizeMessageCreated(source, message); - return _sendMessage(id, sendingStatus(id), message); - } - - /// @inheritdoc IGatewaySource - function createMessage( - string memory source, - string memory destination, - Message memory message - ) external payable virtual returns (bytes32) { - bytes32 id = messageId(source, destination, message); - _validateCurrentChain(source); - _authorizeMessageCreated(source, message); - return _createMessage(id, sendingStatus(id), message); - } - - /// @inheritdoc IGatewaySource - function forwardMessage( - string memory source, - string memory destination, - Message memory message - ) external payable virtual returns (bytes32) { - bytes32 id = messageId(source, destination, message); - _authorizeMessageForwarded(destination, message); - return _forwardMessage(id, sendingStatus(id), message); - } - - /// @dev Internal version of {createMessage} without access control. - function _createMessage( - bytes32 id, - MessageSourceStatus status, - Message memory message - ) internal virtual returns (bytes32) { - // Check if the message was not created or sent before. NOOP otherwise. - if (status == MessageSourceStatus.Unknown) { - emit MessageCreated(id, message); - } - return id; - } - - /// @dev Internal version of {sendMessage} without access control. - function _sendMessage( - bytes32 id, - MessageSourceStatus status, - Message memory message - ) internal virtual returns (bytes32) { - /// Check if the message hwas not sent before. NOOP otherwise. - if (status != MessageSourceStatus.Sent) { - emit MessageSent(id, message); - } - return id; - } - - /// @dev Internal version of {forwardMessage} without access control. - function _forwardMessage( - bytes32 id, - MessageSourceStatus status, - Message memory message - ) internal virtual returns (bytes32) { - // Check if the message was created first. NOOP otherwise. - if (status == MessageSourceStatus.Created) { - _sendMessage(id, status, message); - } - return id; - } - - /// @dev Authorizes the creation of a message on the source chain. - /// - /// Requirements: - /// - The source chain must match `msg.sender` - function _authorizeMessageCreated(string memory source, Message memory message) internal virtual { - CAIP10.Account memory sourceAccount = CAIP10.fromString(source); - - if (CAIP10.getAddress(sourceAccount) != msg.sender) { - revert UnauthorizedSourceMessage(source, msg.sender, message); - } - } - - /// @dev Authorizes the forwarding of a message to the destination chain. - function _authorizeMessageForwarded(string memory destination, Message memory message) internal virtual; - - /// @dev Validates that the source chain is the current chain. - function _validateCurrentChain(string memory source) private view { - CAIP10.Account memory sourceAccount = CAIP10.fromString(source); - if (!CAIP2.isCurrentEVMChain(sourceAccount._chainId)) { - revert MismatchedSourceChain(source); - } - } -} - -abstract contract GatewaySourceGeneric is GatewaySource { - using Set for Set.Bytes32Set; - using CAIP10 for CAIP10.Account; - - Set.Bytes32Set private _createdBox; - Set.Bytes32Set private _sentBox; - - /// @inheritdoc IGatewayCommon - function messageId( - string memory source, - string memory destination, - Message memory message - ) public pure override returns (bytes32) { - return keccak256(abi.encode(source, destination, message)); - } - - /// @inheritdoc IGatewaySource - function sendingStatus(bytes32 id) public view virtual override returns (MessageSourceStatus) { - if (_sentBox.contains(id)) return MessageSourceStatus.Sent; - if (_createdBox.contains(id)) return MessageSourceStatus.Created; - return MessageSourceStatus.Unknown; - } -} diff --git a/contracts/crosschain/ICAIP2Equivalence.sol b/contracts/crosschain/ICAIP2Equivalence.sol index 11e696e5..68ac10af 100644 --- a/contracts/crosschain/ICAIP2Equivalence.sol +++ b/contracts/crosschain/ICAIP2Equivalence.sol @@ -4,18 +4,16 @@ pragma solidity ^0.8.0; import {IAxelarGateway} from "./vendor/axelar/interfaces/IAxelarGateway.sol"; import {IAxelarGasService} from "./vendor/axelar/interfaces/IAxelarGasService.sol"; -import {CAIP2} from "../utils/CAIP-2.sol"; -/// @dev Equivalence interface between CAIP-2 chain identifiers and Gateway-specific chain identifiers. +/// @dev Equivalence interface between CAIP-2 chain identifiers and protocol-specific chain identifiers. /// /// See https://chainagnostic.org/CAIPs/caip-2[CAIP2]. interface ICAIP2Equivalence { - /// @dev Sets a CAIP-2 chain identifier as equivalent to a Gateway-specific chain identifier. - function setCAIP2Equivalence(CAIP2.ChainId memory chain, bytes memory custom) external; + error UnsupportedChain(string caip2); - /// @dev Checks if a CAIP-2 chain identifier is registered as equivalent to a Gateway-specific chain identifier. - function exists(CAIP2.ChainId memory chain) external view returns (bool); + /// @dev Checks if a CAIP-2 chain identifier is registered as equivalent to a protocol-specific chain identifier. + function supported(string memory caip2) external view returns (bool); - /// @dev Retrieves the Gateway-specific chain identifier equivalent to a CAIP-2 chain identifier. - function fromCAIP2(CAIP2.ChainId memory chain) external pure returns (bytes memory); + /// @dev Retrieves the protocol-specific chain identifier equivalent to a CAIP-2 chain identifier. + function fromCAIP2(string memory caip2) external view returns (bytes memory); } diff --git a/contracts/crosschain/IGatewayCommon.sol b/contracts/crosschain/IGatewayCommon.sol deleted file mode 100644 index 2d198ad3..00000000 --- a/contracts/crosschain/IGatewayCommon.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -/// @dev Common for a generic cross-chain gateway. -/// -/// Gateways are split in two parts: source and destination. Respectively, they are responsible for -/// sending and receiving messages between different blockchains. -interface IGatewayCommon { - /// @dev Represents a cross-chain message. - struct Message { - // Arbitrary data to be sent with the message. - bytes payload; - // Extra parameters to be used by the gateway specialization. - bytes extraParams; - } - - /// @dev Uniquely identifies a message. - /// @param source CAIP-10 account ID of the source chain. - /// @param destination CAIP-10 account ID of the destination chain. - function messageId( - string memory source, - string memory destination, - Message memory message - ) external pure returns (bytes32); -} diff --git a/contracts/crosschain/IGatewayDestination.sol b/contracts/crosschain/IGatewayDestination.sol deleted file mode 100644 index 02224cd9..00000000 --- a/contracts/crosschain/IGatewayDestination.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {IGatewayCommon} from "./IGatewayCommon.sol"; - -/// @dev Interface for a cross-chain gateway that receives messages. -/// -/// Allows to check the status of a message and to mark it as delivered or executed. -interface IGatewayDestination is IGatewayCommon { - enum MessageDestinationStatus { - Unknown, - Delivered, - Executed - } - - event MessageDelivered(bytes32 indexed id, Message message); - event MessageExecuted(bytes32 indexed id, Message message); - - error UnauthorizedDestinationMessage(string destination, address sender, Message message); - error MismatchedDestinationChain(string destination); - - /// @dev Returns the status of a received cross-chain message. - function destinationStatus(bytes32 id) external view returns (MessageDestinationStatus); - - /// @dev Sets a cross-chain message as delivered, ready for execution. - /// MessageDestinationStatus.Unknown -> MessageDestinationStatus.Delivered - /// NOTE: Should only be called by an authorized gateway operator and validate that destination is the current chain. - function deliverMessage( - string memory source, - string memory destination, - Message memory message - ) external returns (bytes32); - - /// @dev Sets a cross-chain message as executed. - /// MessageDestinationStatus.Unknown -> MessageDestinationStatus.Executed. - /// MessageDestinationStatus.Delivered -> MessageDestinationStatus.Executed. - /// NOTE: Should only be called by the destination account. - function setMessageExecuted( - string memory source, - string memory destination, - Message memory message - ) external returns (bytes32); -} diff --git a/contracts/crosschain/IGatewayIncoming.sol b/contracts/crosschain/IGatewayIncoming.sol new file mode 100644 index 00000000..cf01f8b6 --- /dev/null +++ b/contracts/crosschain/IGatewayIncoming.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IGatewayIncoming { + event MessageExecuted(bytes32 indexed id); +} + +interface IGatewayIncomingPassive { + function validateReceivedMessage( + bytes32 messageId, + string calldata srcChain, + string calldata srcAccount, + bytes calldata payload, + bytes calldata attributes + ) external; +} diff --git a/contracts/crosschain/IGatewayIncomingPassive.sol b/contracts/crosschain/IGatewayIncomingPassive.sol new file mode 100644 index 00000000..cf01f8b6 --- /dev/null +++ b/contracts/crosschain/IGatewayIncomingPassive.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IGatewayIncoming { + event MessageExecuted(bytes32 indexed id); +} + +interface IGatewayIncomingPassive { + function validateReceivedMessage( + bytes32 messageId, + string calldata srcChain, + string calldata srcAccount, + bytes calldata payload, + bytes calldata attributes + ) external; +} diff --git a/contracts/crosschain/IGatewayOutgoing.sol b/contracts/crosschain/IGatewayOutgoing.sol new file mode 100644 index 00000000..69079ac9 --- /dev/null +++ b/contracts/crosschain/IGatewayOutgoing.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IGatewayOutgoing { + struct Message { + string source; // CAIP-10 account ID + string destination; // CAIP-10 account ID + bytes payload; + bytes attributes; + } + + event MessageCreated(bytes32 indexed id, Message message); + event MessageSent(bytes32 indexed id); + + function sendMessage( + string calldata destChain, // CAIP-2 chain ID + string calldata destAccount, // i.e. address + bytes calldata payload, + bytes calldata attributes + ) external payable returns (bytes32 messageId); +} diff --git a/contracts/crosschain/IGatewayReceiver.sol b/contracts/crosschain/IGatewayReceiver.sol new file mode 100644 index 00000000..6035ebfe --- /dev/null +++ b/contracts/crosschain/IGatewayReceiver.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IGatewayReceiver { + function receiveMessage( + bytes32 messageId, + string calldata srcChain, + string calldata srcAccount, + bytes calldata payload, + bytes calldata attributes + ) external payable; +} diff --git a/contracts/crosschain/IGatewaySource.sol b/contracts/crosschain/IGatewaySource.sol deleted file mode 100644 index ba4c12b1..00000000 --- a/contracts/crosschain/IGatewaySource.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {IGatewayCommon} from "./IGatewayCommon.sol"; - -/// @dev Interface for a cross-chain gateway that sends messages. -/// -/// Allows for 2 sending modes: {sendMessage} and {createMessage} + {forwardMessage}, -/// where the latter allows to hook logic before sending the message. -interface IGatewaySource is IGatewayCommon { - enum MessageSourceStatus { - Unknown, - Created, - Sent - } - - event MessageCreated(bytes32 indexed id, Message message); - event MessageSent(bytes32 indexed id, Message message); - - error UnauthorizedSourceMessage(string source, address sender, Message message); - error MismatchedSourceChain(string source); - - /// @dev Returns the status of a sent cross-chain message. - function sendingStatus(bytes32 id) external view returns (MessageSourceStatus); - - /// @dev Send a cross-chain message to the target chain. - /// MessageSourceStatus.Unknown -> MessageSourceStatus.Sent - /// NOTE: Must validate that source is the current chain. - function sendMessage( - string memory source, - string memory destination, - Message memory message - ) external payable returns (bytes32); - - /// @dev Create a cross-chain message to the target chain. See {forwardMessage} to send it. - /// MessageSourceStatus.Unknown -> MessageSourceStatus.Created - /// NOTE: Must validate that source is the current chain. - function createMessage( - string memory source, - string memory destination, - Message memory message - ) external payable returns (bytes32); - - /// @dev Forwards a previously created cross-chain message to the target chain. See {createMessage} to create it. - /// MessageSourceStatus.Created -> MessageSourceStatus.Sent - function forwardMessage( - string memory source, - string memory destination, - Message memory message - ) external payable returns (bytes32); -} diff --git a/contracts/crosschain/axelar/AxelarCAIP2Equivalence.sol b/contracts/crosschain/axelar/AxelarCAIP2Equivalence.sol new file mode 100644 index 00000000..aa17c3c3 --- /dev/null +++ b/contracts/crosschain/axelar/AxelarCAIP2Equivalence.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {ICAIP2Equivalence} from "../ICAIP2Equivalence.sol"; + +abstract contract AxelarCAIP2Equivalence is ICAIP2Equivalence { + mapping(string caip2 => string destinationChain) private _equivalence; + + function supported(string memory caip2) public view returns (bool) { + return bytes(_equivalence[caip2]).length != 0; + } + + function fromCAIP2(string memory caip2) public view returns (bytes memory) { + return bytes(_equivalence[caip2]); + } +} + +// EVM (https://axelarscan.io/resources/chains?type=evm) +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("1"))] = "Ethereum"; +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("56"))] = "binance"; +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("137"))] = "Polygon"; +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("43114"))] = "Avalanche"; +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("250"))] = "Fantom"; +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("1284"))] = "Moonbeam"; +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("1313161554"))] = "aurora"; +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("42161"))] = "arbitrum"; +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("10"))] = "optimism"; +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("8453"))] = "base"; +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("5000"))] = "mantle"; +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("42220"))] = "celo"; +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("2222"))] = "kava"; +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("314"))] = "filecoin"; +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("59144"))] = "linea"; +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("2031"))] = "centrifuge"; +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("534352"))] = "scroll"; +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("13371"))] = "immutable"; +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("252"))] = "fraxtal"; +// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("81457"))] = "blast"; + +// Cosmos (https://axelarscan.io/resources/chains?type=cosmos) +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('axelar-dojo-1'))] = 'Axelarnet'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('osmosis-1'))] = 'osmosis'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('cosmoshub-4'))] = 'cosmoshub'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('juno-1'))] = 'juno'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('emoney-3'))] = 'e-money'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('injective-1'))] = 'injective'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('crescent-1'))] = 'crescent'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('kaiyo-1'))] = 'kujira'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('secret-4'))] = 'secret-snip'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('secret-4'))] = 'secret'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('pacific-1'))] = 'sei'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('stargaze-1'))] = 'stargaze'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('mantle-1'))] = 'assetmantle'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('fetchhub-4'))] = 'fetch'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('kichain-2'))] = 'ki'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('evmos_9001-2'))] = 'evmos'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('xstaxy-1'))] = 'aura'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('comdex-1'))] = 'comdex'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('core-1'))] = 'persistence'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('regen-1'))] = 'regen'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('umee-1'))] = 'umee'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('agoric-3'))] = 'agoric'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('dimension_37-1'))] = 'xpla'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('acre_9052-1'))] = 'acre'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('stride-1'))] = 'stride'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('carbon-1'))] = 'carbon'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('sommelier-3'))] = 'sommelier'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('neutron-1'))] = 'neutron'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('reb_1111-1'))] = 'rebus'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('archway-1'))] = 'archway'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('pio-mainnet-1'))] = 'provenance'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('ixo-5'))] = 'ixo'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('migaloo-1'))] = 'migaloo'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('teritori-1'))] = 'teritori'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('haqq_11235-1'))] = 'haqq'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('celestia'))] = 'celestia'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('agamotto'))] = 'ojo'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('chihuahua-1'))] = 'chihuahua'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('ssc-1'))] = 'saga'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('dymension_1100-1'))] = 'dymension'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('fxcore'))] = 'fxcore'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('perun-1'))] = 'c4e'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('bitsong-2b'))] = 'bitsong'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('pirin-1'))] = 'nolus'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('lava-mainnet-1'))] = 'lava'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('phoenix-1'))] = 'terra-2'; +// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('columbus-5'))] = 'terra';" diff --git a/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol b/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol new file mode 100644 index 00000000..3865e55d --- /dev/null +++ b/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {IAxelarGateway} from "../vendor/axelar/interfaces/IAxelarGateway.sol"; +import {IAxelarGasService} from "../vendor/axelar/interfaces/IAxelarGasService.sol"; +import {IGatewayOutgoing} from "../IGatewayOutgoing.sol"; +import {AxelarCAIP2Equivalence} from "./AxelarCAIP2Equivalence.sol"; +import {CAIP2} from "../../utils/CAIP-2.sol"; +import {CAIP10} from "../../utils/CAIP-10.sol"; + +abstract contract AxelarGatewayOutgoing is IGatewayOutgoing, AxelarCAIP2Equivalence { + IAxelarGateway public immutable axelarGateway; + + function sendMessage( + string calldata destChain, // CAIP-2 chain ID + string calldata destAccount, // i.e. address + bytes calldata payload, + bytes calldata /* attributes */ + ) external payable override returns (bytes32 messageId) { + // TODO: Handle ether (payable) + if (!supported(destChain)) revert UnsupportedChain(destChain); + axelarGateway.callContract(string(fromCAIP2(destChain)), destAccount, payload); + return keccak256(payload); + } +} diff --git a/contracts/crosschain/axelar/GatewayAxelarCAIP2.sol b/contracts/crosschain/axelar/GatewayAxelarCAIP2.sol deleted file mode 100644 index 6fa778fb..00000000 --- a/contracts/crosschain/axelar/GatewayAxelarCAIP2.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {ICAIP2Equivalence} from "../ICAIP2Equivalence.sol"; -import {CAIP2} from "../../utils/CAIP-2.sol"; - -abstract contract GatewayAxelarCAIP2 is ICAIP2Equivalence { - error AlreadyRegisteredChain(CAIP2.ChainId chain); - error UnsupportedChain(CAIP2.ChainId chain); - - mapping(CAIP2.Chain chain => AxelarChain chainName) public chainDetails; - - struct AxelarChain { - string destinationChain; - string contractAddress; - } - - function isRegisteredCAIP2(CAIP2.ChainId memory chain) public view override returns (bool) { - return bytes(chainDetails[chain].destinationChain).length != 0; - } - - function fromCAIP2(CAIP2.ChainId memory chain) public pure returns (bytes memory) { - return abi.encode(AxelarChain(chain.destinationChain, chain.contractAddress)); - } - - function registerCAIP2Equivalence(CAIP2.ChainId memory chain, bytes memory custom) public onlyOwner { - if (isRegisteredCAIP2(chain)) { - revert AlreadyRegisteredChain(chain); - } - chainDetails[chain] = abi.decode(custom, (AxelarChain)); - } -} diff --git a/contracts/crosschain/axelar/GatewayAxelarSource.sol b/contracts/crosschain/axelar/GatewayAxelarSource.sol deleted file mode 100644 index b5347669..00000000 --- a/contracts/crosschain/axelar/GatewayAxelarSource.sol +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {IAxelarGateway} from "../vendor/axelar/interfaces/IAxelarGateway.sol"; -import {IAxelarGasService} from "../vendor/axelar/interfaces/IAxelarGasService.sol"; - -import {GatewaySource} from "../GatewaySource.sol"; -import {GatewayAxelarCAIP2} from "./GatewayAxelarCAIP2.sol"; -import {CAIP10} from "../../utils/CAIP-10.sol"; - -contract GatewayAxelarSource is GatewaySource, GatewayAxelarCAIP2 { - IAxelarGateway public immutable axelarGateway; - IAxelarGasService public immutable gasService; - - constructor(IAxelarGateway _axelarGateway, address _initialOwner) Ownable(_initialOwner) { - axelarGateway = _axelarGateway; - } - - /// @inheritdoc GatewayDestination - function messageId( - string memory source, - string memory destination, - Message memory message - ) public pure override returns (bytes32) { - return keccak256(abi.encode(source, destination, message)); - } - - /// @inheritdoc GatewayDestination - function destinationStatus(bytes32 id) public view override returns (MessageDestinationStatus) { - if (axelarGateway.isCommandExecuted(commandId)) return MessageDestinationStatus.Executed; - if (_deliveredBox.contains(id)) return MessageDestinationStatus.Delivered; - return MessageDestinationStatus.Unknown; - } - - function _authorizeMessageCreated( - string memory source, - Message memory message - ) internal override returns (bytes32) { - if (!isRegisteredCAIP2(CAIP10.fromString(destination)._chainId)) { - revert UnsupportedChain(chain); - } - - return super._authorizeMessageCreated(source, message); - } - - function _sendMessage( - bytes32 id, - string memory /* source */, - string memory destination, - MessageSourceStatus status, - Message memory message - ) internal override returns (bytes32) { - super._sendMessage(id, status, message); - - if (status != MessageSourceStatus.Sent) { - AxelarChain memory details = chainDetails[CAIP10.fromString(destination)._chainId]; - axelarGateway.callContract(details.destinationChain, details.contractAddress, payload); - } - - return id; - } -} diff --git a/contracts/crosschain/generic/GatewayDestinationGeneric.sol b/contracts/crosschain/generic/GatewayDestinationGeneric.sol deleted file mode 100644 index 9e39cc85..00000000 --- a/contracts/crosschain/generic/GatewayDestinationGeneric.sol +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {CAIP10} from "../../utils/CAIP-10.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {GatewayDestination} from "../GatewayDestination.sol"; -import {Set} from "../../utils/Set.sol"; - -contract GatewayDestinationGeneric is GatewayDestination, Ownable { - using Set for Set.Bytes32Set; - Set.Bytes32Set private _executedBox; - Set.Bytes32Set private _deliveredBox; - - constructor() Ownable(msg.sender) {} - - /// @inheritdoc GatewayDestination - function messageId( - string memory source, - string memory destination, - Message memory message - ) public pure override returns (bytes32) { - return keccak256(abi.encode(source, destination, message)); - } - - /// @inheritdoc GatewayDestination - function destinationStatus(bytes32 id) public view override returns (MessageDestinationStatus) { - if (_executedBox.contains(id)) return MessageDestinationStatus.Executed; - if (_deliveredBox.contains(id)) return MessageDestinationStatus.Delivered; - return MessageDestinationStatus.Unknown; - } - - /// @inheritdoc GatewayDestination - function _authorizeMessageDelivered( - string memory destination, - Message memory message - ) internal virtual override onlyOwner {} - - /// @inheritdoc GatewayDestination - function _deliverMessage( - bytes32 id, - MessageDestinationStatus, - Message memory - ) internal virtual override returns (bytes32) { - _deliveredBox.insert(id); // Idempotent - return id; - } - - /// @inheritdoc GatewayDestination - function _setMessageExecuted( - bytes32 id, - MessageDestinationStatus, - Message memory - ) internal virtual override returns (bytes32) { - _executedBox.insert(id); // Idempotent - return id; - } -} diff --git a/contracts/crosschain/generic/GatewaySourceGeneric.sol b/contracts/crosschain/generic/GatewaySourceGeneric.sol deleted file mode 100644 index 49bcaae2..00000000 --- a/contracts/crosschain/generic/GatewaySourceGeneric.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {CAIP10} from "../../utils/CAIP-10.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {GatewaySource} from "../GatewaySource.sol"; -import {Set} from "../../utils/Set.sol"; - -contract GatewaySourceGeneric is GatewaySource, Ownable { - using Set for Set.Bytes32Set; - using CAIP10 for CAIP10.Account; - - Set.Bytes32Set private _createdBox; - Set.Bytes32Set private _sentBox; - - constructor() Ownable(msg.sender) {} - - /// @inheritdoc GatewaySource - function messageId( - string memory source, - string memory destination, - Message memory message - ) public pure override returns (bytes32) { - return keccak256(abi.encode(source, destination, message)); - } - - /// @inheritdoc GatewaySource - function sendingStatus(bytes32 id) public view virtual override returns (MessageSourceStatus) { - if (_sentBox.contains(id)) return MessageSourceStatus.Sent; - if (_createdBox.contains(id)) return MessageSourceStatus.Created; - return MessageSourceStatus.Unknown; - } - - /// @inheritdoc GatewaySource - function _authorizeMessageForwarded( - string memory destination, - Message memory message - ) internal virtual override onlyOwner {} - - /// @inheritdoc GatewaySource - function _createMessage( - bytes32 id, - MessageSourceStatus, - Message memory - ) internal virtual override returns (bytes32) { - _createdBox.insert(id); - return id; - } - - /// @inheritdoc GatewaySource - function _sendMessage(bytes32 id, MessageSourceStatus, Message memory) internal virtual override returns (bytes32) { - _sentBox.insert(id); - return id; - } -} diff --git a/contracts/utils/CAIP-10.sol b/contracts/utils/CAIP-10.sol index a9c26c2f..d190d95f 100644 --- a/contracts/utils/CAIP-10.sol +++ b/contracts/utils/CAIP-10.sol @@ -2,46 +2,37 @@ pragma solidity ^0.8.0; -import {CAIP2} from "./CAIP-2.sol"; - +// account_id: chain_id + ":" + account_address +// chain_id: [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See [CAIP-2][]) +// account_address: [-.%a-zA-Z0-9]{1,128} library CAIP10 { - using CAIP2 for CAIP2.ChainId; - - // account_id: chain_id + ":" + account_address - // chain_id: [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See [CAIP-2][]) - // account_address: [-.%a-zA-Z0-9]{1,128} - struct Account { - CAIP2.ChainId _chainId; - string _accountId; // Often referred to as address - } + bytes1 constant SEMICOLON = ":"; - function toString(Account memory account) internal pure returns (string memory) { - return string(abi.encodePacked(account._chainId.toString(), CAIP2.SEMICOLON, account._accountId)); + function toString(bytes32 chainId, string memory accountId) internal pure returns (string memory) { + return string(abi.encodePacked(chainId, SEMICOLON, accountId)); } - function fromString(string memory accountStr) internal pure returns (Account memory account) { + function fromString(string memory accountStr) internal pure returns (string memory caip2, string memory accountId) { bytes memory accountBuffer = bytes(accountStr); uint256 lastSeparatorIndex = _findLastSeparatorIndex(accountBuffer); - account._chainId = extractChainId(accountBuffer, lastSeparatorIndex); - account._accountId = extractAccountId(accountBuffer, lastSeparatorIndex); - return account; + return (_extractCAIP2(accountBuffer, lastSeparatorIndex), _extractAccountId(accountBuffer, lastSeparatorIndex)); } - function extractChainId( + function _extractCAIP2( bytes memory accountBuffer, uint256 lastSeparatorIndex - ) internal pure returns (CAIP2.ChainId memory chainId) { + ) private pure returns (string memory chainId) { bytes memory _chainId = new bytes(lastSeparatorIndex); for (uint256 i = 0; i < lastSeparatorIndex; i++) { _chainId[i] = accountBuffer[i]; } - return CAIP2.fromString(string(_chainId)); + return string(_chainId); } - function extractAccountId( + function _extractAccountId( bytes memory accountBuffer, uint256 lastSeparatorIndex - ) internal pure returns (string memory) { + ) private pure returns (string memory) { uint256 length = accountBuffer.length; uint256 offset = lastSeparatorIndex - 1; bytes memory _accountId = new bytes(length - offset); // Will overflow if no separator is found @@ -51,13 +42,9 @@ library CAIP10 { return string(_accountId); } - function getAddress(Account memory account) internal pure returns (address) { - return address(bytes20(bytes(account._accountId))); - } - function _findLastSeparatorIndex(bytes memory accountBuffer) private pure returns (uint256) { for (uint256 i = accountBuffer.length - 1; i >= 0; i--) { - if (accountBuffer[i] == CAIP2.SEMICOLON) { + if (accountBuffer[i] == SEMICOLON) { return i; } } diff --git a/contracts/utils/CAIP-2.sol b/contracts/utils/CAIP-2.sol index 9a7ae9a8..2b921ad4 100644 --- a/contracts/utils/CAIP-2.sol +++ b/contracts/utils/CAIP-2.sol @@ -5,33 +5,46 @@ pragma solidity ^0.8.0; import {Arrays} from "@openzeppelin/contracts/utils/Arrays.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +// chain_id: namespace + ":" + reference +// namespace: [-a-z0-9]{3,8} +// reference: [-_a-zA-Z0-9]{1,32} library CAIP2 { bytes16 private constant HEX_DIGITS = "0123456789abcdef"; - - // chain_id: namespace + ":" + reference - // namespace: [-a-z0-9]{3,8} - // reference: [-_a-zA-Z0-9]{1,32} - struct ChainId { - bytes8 _namespace; - bytes32 _reference; - } - bytes1 constant SEMICOLON = ":"; - error TooLongChainId(); - - function toString(ChainId memory chainId) internal pure returns (string memory) { - return string(abi.encodePacked(chainId._namespace, SEMICOLON, chainId._reference)); + function toString(bytes8 namespace, bytes32 ref) internal pure returns (string memory) { + return string(abi.encodePacked(namespace, SEMICOLON, ref)); } /// @dev Parses a chain ID from a string by splitting it at the first semicolon. /// The function parses both sides as `bytes8` and `bytes32` respectively wiothout any validation. - function fromString(string memory chainStr) internal pure returns (ChainId memory chainId) { + function fromString(string memory chainStr) internal pure returns (bytes8 namespace, bytes32 ref) { bytes memory chainBuffer = bytes(chainStr); uint8 semicolonIndex = _findSemicolonIndex(chainBuffer); - chainId._namespace = _extractNamespace(chainBuffer, semicolonIndex); - chainId._reference = _unsafeExtractReference(chainBuffer, semicolonIndex); - return chainId; + return (_extractNamespace(chainBuffer, semicolonIndex), _unsafeExtractReference(chainBuffer, semicolonIndex)); + } + + function isCurrentEVMChain(string memory chainStr) internal view returns (bool) { + (bytes8 namespace, bytes32 ref) = fromString(chainStr); + return + namespace == currentChainId() && // Chain ID must match the current chain + ref == bytes32(bytes(string("eip155"))); // EIP-155 for EVM chains + } + + /// @dev Returns the chain ID of the current chain. + /// Assumes block.chainId < type(uint64).max + function currentChainId() internal view returns (bytes8 _chainId) { + unchecked { + uint256 id = block.chainid; + while (true) { + _chainId = bytes8(uint64(_chainId) - 1); + assembly ("memory-safe") { + mstore8(_chainId, byte(mod(id, 10), HEX_DIGITS)) + } + id /= 10; + if (id == 0) break; + } + } } /// @dev Extracts the first `semicolonIndex` bytes from the chain buffer as a bytes8 namespace. @@ -63,26 +76,4 @@ library CAIP2 { } return length; } - - function isCurrentEVMChain(ChainId memory chain) internal view returns (bool) { - return - chain._namespace == currentChainId() && // Chain ID must match the current chain - chain._reference == bytes32(bytes(string("eip155"))); // EIP-155 for EVM chains - } - - /// @dev Returns the chain ID of the current chain. - /// Assumes block.chainId < type(uint64).max - function currentChainId() internal view returns (bytes8 _chainId) { - unchecked { - uint256 id = block.chainid; - while (true) { - _chainId = bytes8(uint64(_chainId) - 1); - assembly ("memory-safe") { - mstore8(_chainId, byte(mod(id, 10), HEX_DIGITS)) - } - id /= 10; - if (id == 0) break; - } - } - } } From 597bfc248b0c1add0485bfe37f3cc1a214b600d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 26 Aug 2024 14:10:37 -0600 Subject: [PATCH 13/51] Remove interfaces --- contracts/crosschain/IGatewayIncoming.sol | 10 ---------- contracts/crosschain/IGatewayIncomingPassive.sol | 4 ---- 2 files changed, 14 deletions(-) diff --git a/contracts/crosschain/IGatewayIncoming.sol b/contracts/crosschain/IGatewayIncoming.sol index cf01f8b6..b67eb536 100644 --- a/contracts/crosschain/IGatewayIncoming.sol +++ b/contracts/crosschain/IGatewayIncoming.sol @@ -5,13 +5,3 @@ pragma solidity ^0.8.0; interface IGatewayIncoming { event MessageExecuted(bytes32 indexed id); } - -interface IGatewayIncomingPassive { - function validateReceivedMessage( - bytes32 messageId, - string calldata srcChain, - string calldata srcAccount, - bytes calldata payload, - bytes calldata attributes - ) external; -} diff --git a/contracts/crosschain/IGatewayIncomingPassive.sol b/contracts/crosschain/IGatewayIncomingPassive.sol index cf01f8b6..b8cbca21 100644 --- a/contracts/crosschain/IGatewayIncomingPassive.sol +++ b/contracts/crosschain/IGatewayIncomingPassive.sol @@ -2,10 +2,6 @@ pragma solidity ^0.8.0; -interface IGatewayIncoming { - event MessageExecuted(bytes32 indexed id); -} - interface IGatewayIncomingPassive { function validateReceivedMessage( bytes32 messageId, From 2951fa5d28ae590420aeabb69651ca6bd88cb140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 26 Aug 2024 15:27:22 -0600 Subject: [PATCH 14/51] Checkpoint --- contracts/crosschain/IGatewayOutgoing.sol | 4 +- .../axelar/AxelarGatewayOutgoing.sol | 29 ++++++++++++--- contracts/utils/CAIP-10.sol | 13 +++++-- contracts/utils/CAIP-2.sol | 37 ++++++++++++------- 4 files changed, 60 insertions(+), 23 deletions(-) diff --git a/contracts/crosschain/IGatewayOutgoing.sol b/contracts/crosschain/IGatewayOutgoing.sol index 69079ac9..ab90553c 100644 --- a/contracts/crosschain/IGatewayOutgoing.sol +++ b/contracts/crosschain/IGatewayOutgoing.sol @@ -6,7 +6,7 @@ interface IGatewayOutgoing { struct Message { string source; // CAIP-10 account ID string destination; // CAIP-10 account ID - bytes payload; + bytes data; bytes attributes; } @@ -16,7 +16,7 @@ interface IGatewayOutgoing { function sendMessage( string calldata destChain, // CAIP-2 chain ID string calldata destAccount, // i.e. address - bytes calldata payload, + bytes calldata data, bytes calldata attributes ) external payable returns (bytes32 messageId); } diff --git a/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol b/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol index 3865e55d..c384d4d8 100644 --- a/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol +++ b/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.0; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {IAxelarGateway} from "../vendor/axelar/interfaces/IAxelarGateway.sol"; import {IAxelarGasService} from "../vendor/axelar/interfaces/IAxelarGasService.sol"; import {IGatewayOutgoing} from "../IGatewayOutgoing.sol"; @@ -15,12 +16,30 @@ abstract contract AxelarGatewayOutgoing is IGatewayOutgoing, AxelarCAIP2Equivale function sendMessage( string calldata destChain, // CAIP-2 chain ID string calldata destAccount, // i.e. address - bytes calldata payload, - bytes calldata /* attributes */ + bytes calldata data, + bytes calldata attributes ) external payable override returns (bytes32 messageId) { // TODO: Handle ether (payable) - if (!supported(destChain)) revert UnsupportedChain(destChain); - axelarGateway.callContract(string(fromCAIP2(destChain)), destAccount, payload); - return keccak256(payload); + // TODO: Validate attributes + + // Validate there's an equivalent chain identifier supported by the gateway + string memory destinationCAIP2 = CAIP2.toString(destChain); + if (!supported(destinationCAIP2)) revert UnsupportedChain(destinationCAIP2); + + // Create a message + Message memory message = Message( + CAIP10.currentId(Strings.toHexString(msg.sender)), + CAIP10.toString(destinationCAIP2, destAccount), + data, + attributes + ); + bytes32 id = keccak256(message); + emit MessageCreated(id, message); + + // Send the message + axelarGateway.callContract(string(fromCAIP2(destination)), destAccount, data); + emit MessageSent(id); + + return id; } } diff --git a/contracts/utils/CAIP-10.sol b/contracts/utils/CAIP-10.sol index d190d95f..46e50c06 100644 --- a/contracts/utils/CAIP-10.sol +++ b/contracts/utils/CAIP-10.sol @@ -2,14 +2,16 @@ pragma solidity ^0.8.0; +import {CAIP2} from "./CAIP-2.sol"; + // account_id: chain_id + ":" + account_address // chain_id: [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See [CAIP-2][]) // account_address: [-.%a-zA-Z0-9]{1,128} library CAIP10 { - bytes1 constant SEMICOLON = ":"; + bytes1 private constant SEMICOLON = ":"; - function toString(bytes32 chainId, string memory accountId) internal pure returns (string memory) { - return string(abi.encodePacked(chainId, SEMICOLON, accountId)); + function toString(string memory caip2, string memory accountId) internal pure returns (string memory) { + return string(abi.encodePacked(caip2, SEMICOLON, accountId)); } function fromString(string memory accountStr) internal pure returns (string memory caip2, string memory accountId) { @@ -18,6 +20,11 @@ library CAIP10 { return (_extractCAIP2(accountBuffer, lastSeparatorIndex), _extractAccountId(accountBuffer, lastSeparatorIndex)); } + function currentId(string memory accountId) internal view returns (string memory) { + (bytes8 namespace, bytes32 ref) = CAIP2.currentId(); + return toString(CAIP2.toString(namespace, ref), accountId); + } + function _extractCAIP2( bytes memory accountBuffer, uint256 lastSeparatorIndex diff --git a/contracts/utils/CAIP-2.sol b/contracts/utils/CAIP-2.sol index 2b921ad4..3d0053c4 100644 --- a/contracts/utils/CAIP-2.sol +++ b/contracts/utils/CAIP-2.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.0; -import {Arrays} from "@openzeppelin/contracts/utils/Arrays.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; // chain_id: namespace + ":" + reference @@ -10,30 +9,37 @@ import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; // reference: [-_a-zA-Z0-9]{1,32} library CAIP2 { bytes16 private constant HEX_DIGITS = "0123456789abcdef"; - bytes1 constant SEMICOLON = ":"; + bytes1 private constant SEMICOLON = ":"; + bytes32 private constant EVM_REFERENCE = bytes32("eip155"); // EIP-155 for EVM chains + /// @dev Converts a namespace and reference to a CAIP2 chain identifier. function toString(bytes8 namespace, bytes32 ref) internal pure returns (string memory) { return string(abi.encodePacked(namespace, SEMICOLON, ref)); } - /// @dev Parses a chain ID from a string by splitting it at the first semicolon. - /// The function parses both sides as `bytes8` and `bytes32` respectively wiothout any validation. - function fromString(string memory chainStr) internal pure returns (bytes8 namespace, bytes32 ref) { - bytes memory chainBuffer = bytes(chainStr); + /// @dev Parses a CAIP2 identifier from a string by splitting it at the first semicolon. + /// The function parses both sides as `bytes8` and `bytes32` respectively without any validation. + function fromString(string memory caip2) internal pure returns (bytes8 namespace, bytes32 ref) { + bytes memory chainBuffer = bytes(caip2); uint8 semicolonIndex = _findSemicolonIndex(chainBuffer); return (_extractNamespace(chainBuffer, semicolonIndex), _unsafeExtractReference(chainBuffer, semicolonIndex)); } - function isCurrentEVMChain(string memory chainStr) internal view returns (bool) { - (bytes8 namespace, bytes32 ref) = fromString(chainStr); - return - namespace == currentChainId() && // Chain ID must match the current chain - ref == bytes32(bytes(string("eip155"))); // EIP-155 for EVM chains + /// @dev Checks if the given CAIP2 identifier is the current chain. + function isCurrentId(string memory caip2) internal view returns (bool) { + (bytes8 namespace, bytes32 ref) = fromString(caip2); + (bytes8 currentNamespace, bytes32 currentRef) = currentId(); + return namespace == currentNamespace && ref == currentRef; } - /// @dev Returns the chain ID of the current chain. + /// @dev Returns the CAIP2 identifier of the current chain. + function currentId() internal view returns (bytes8 namespace, bytes32 ref) { + return (currentNamespace(), currentReference()); + } + + /// @dev Returns the CAIP2 identifier of the current chain. /// Assumes block.chainId < type(uint64).max - function currentChainId() internal view returns (bytes8 _chainId) { + function currentNamespace() internal view returns (bytes8 _chainId) { unchecked { uint256 id = block.chainid; while (true) { @@ -47,6 +53,11 @@ library CAIP2 { } } + /// @dev Returns the reference of the current chain. + function currentReference() internal pure returns (bytes32) { + return EVM_REFERENCE; + } + /// @dev Extracts the first `semicolonIndex` bytes from the chain buffer as a bytes8 namespace. function _extractNamespace(bytes memory chainBuffer, uint8 semicolonIndex) private pure returns (bytes8 namespace) { assembly ("memory-safe") { From a7bd1309cfcfdbd10b3b39ab0d5c03753c679858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 26 Aug 2024 16:38:20 -0600 Subject: [PATCH 15/51] Add incoming dual mode --- .../crosschain/IGatewayIncomingPassive.sol | 2 + contracts/crosschain/IGatewayOutgoing.sol | 4 +- .../axelar/AxelarCAIP2Equivalence.sol | 134 +++++++++--------- .../axelar/AxelarGatewayIncoming.sol | 56 ++++++++ .../axelar/AxelarGatewayOutgoing.sol | 13 +- 5 files changed, 133 insertions(+), 76 deletions(-) create mode 100644 contracts/crosschain/axelar/AxelarGatewayIncoming.sol diff --git a/contracts/crosschain/IGatewayIncomingPassive.sol b/contracts/crosschain/IGatewayIncomingPassive.sol index b8cbca21..d999e1e6 100644 --- a/contracts/crosschain/IGatewayIncomingPassive.sol +++ b/contracts/crosschain/IGatewayIncomingPassive.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.0; interface IGatewayIncomingPassive { + error GatewayIncomingPassiveInvalidMessage(bytes32 messageId); + function validateReceivedMessage( bytes32 messageId, string calldata srcChain, diff --git a/contracts/crosschain/IGatewayOutgoing.sol b/contracts/crosschain/IGatewayOutgoing.sol index ab90553c..69079ac9 100644 --- a/contracts/crosschain/IGatewayOutgoing.sol +++ b/contracts/crosschain/IGatewayOutgoing.sol @@ -6,7 +6,7 @@ interface IGatewayOutgoing { struct Message { string source; // CAIP-10 account ID string destination; // CAIP-10 account ID - bytes data; + bytes payload; bytes attributes; } @@ -16,7 +16,7 @@ interface IGatewayOutgoing { function sendMessage( string calldata destChain, // CAIP-2 chain ID string calldata destAccount, // i.e. address - bytes calldata data, + bytes calldata payload, bytes calldata attributes ) external payable returns (bytes32 messageId); } diff --git a/contracts/crosschain/axelar/AxelarCAIP2Equivalence.sol b/contracts/crosschain/axelar/AxelarCAIP2Equivalence.sol index aa17c3c3..35eb4eb4 100644 --- a/contracts/crosschain/axelar/AxelarCAIP2Equivalence.sol +++ b/contracts/crosschain/axelar/AxelarCAIP2Equivalence.sol @@ -17,72 +17,72 @@ abstract contract AxelarCAIP2Equivalence is ICAIP2Equivalence { } // EVM (https://axelarscan.io/resources/chains?type=evm) -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("1"))] = "Ethereum"; -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("56"))] = "binance"; -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("137"))] = "Polygon"; -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("43114"))] = "Avalanche"; -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("250"))] = "Fantom"; -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("1284"))] = "Moonbeam"; -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("1313161554"))] = "aurora"; -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("42161"))] = "arbitrum"; -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("10"))] = "optimism"; -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("8453"))] = "base"; -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("5000"))] = "mantle"; -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("42220"))] = "celo"; -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("2222"))] = "kava"; -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("314"))] = "filecoin"; -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("59144"))] = "linea"; -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("2031"))] = "centrifuge"; -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("534352"))] = "scroll"; -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("13371"))] = "immutable"; -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("252"))] = "fraxtal"; -// _equivalence[CAIP2.ChainId(bytes8("eip155"), bytes32("81457"))] = "blast"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("1"))] = "Ethereum"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("56"))] = "binance"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("137"))] = "Polygon"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("43114"))] = "Avalanche"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("250"))] = "Fantom"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("1284"))] = "Moonbeam"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("1313161554"))] = "aurora"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("42161"))] = "arbitrum"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("10"))] = "optimism"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("8453"))] = "base"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("5000"))] = "mantle"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("42220"))] = "celo"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("2222"))] = "kava"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("314"))] = "filecoin"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("59144"))] = "linea"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("2031"))] = "centrifuge"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("534352"))] = "scroll"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("13371"))] = "immutable"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("252"))] = "fraxtal"; +// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("81457"))] = "blast"; // Cosmos (https://axelarscan.io/resources/chains?type=cosmos) -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('axelar-dojo-1'))] = 'Axelarnet'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('osmosis-1'))] = 'osmosis'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('cosmoshub-4'))] = 'cosmoshub'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('juno-1'))] = 'juno'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('emoney-3'))] = 'e-money'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('injective-1'))] = 'injective'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('crescent-1'))] = 'crescent'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('kaiyo-1'))] = 'kujira'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('secret-4'))] = 'secret-snip'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('secret-4'))] = 'secret'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('pacific-1'))] = 'sei'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('stargaze-1'))] = 'stargaze'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('mantle-1'))] = 'assetmantle'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('fetchhub-4'))] = 'fetch'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('kichain-2'))] = 'ki'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('evmos_9001-2'))] = 'evmos'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('xstaxy-1'))] = 'aura'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('comdex-1'))] = 'comdex'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('core-1'))] = 'persistence'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('regen-1'))] = 'regen'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('umee-1'))] = 'umee'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('agoric-3'))] = 'agoric'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('dimension_37-1'))] = 'xpla'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('acre_9052-1'))] = 'acre'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('stride-1'))] = 'stride'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('carbon-1'))] = 'carbon'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('sommelier-3'))] = 'sommelier'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('neutron-1'))] = 'neutron'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('reb_1111-1'))] = 'rebus'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('archway-1'))] = 'archway'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('pio-mainnet-1'))] = 'provenance'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('ixo-5'))] = 'ixo'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('migaloo-1'))] = 'migaloo'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('teritori-1'))] = 'teritori'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('haqq_11235-1'))] = 'haqq'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('celestia'))] = 'celestia'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('agamotto'))] = 'ojo'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('chihuahua-1'))] = 'chihuahua'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('ssc-1'))] = 'saga'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('dymension_1100-1'))] = 'dymension'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('fxcore'))] = 'fxcore'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('perun-1'))] = 'c4e'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('bitsong-2b'))] = 'bitsong'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('pirin-1'))] = 'nolus'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('lava-mainnet-1'))] = 'lava'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('phoenix-1'))] = 'terra-2'; -// _equivalence[CAIP2.ChainId(bytes8('cosmos'), bytes32('columbus-5'))] = 'terra';" +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('axelar-dojo-1'))] = 'Axelarnet'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('osmosis-1'))] = 'osmosis'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('cosmoshub-4'))] = 'cosmoshub'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('juno-1'))] = 'juno'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('emoney-3'))] = 'e-money'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('injective-1'))] = 'injective'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('crescent-1'))] = 'crescent'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('kaiyo-1'))] = 'kujira'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('secret-4'))] = 'secret-snip'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('secret-4'))] = 'secret'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('pacific-1'))] = 'sei'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('stargaze-1'))] = 'stargaze'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('mantle-1'))] = 'assetmantle'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('fetchhub-4'))] = 'fetch'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('kichain-2'))] = 'ki'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('evmos_9001-2'))] = 'evmos'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('xstaxy-1'))] = 'aura'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('comdex-1'))] = 'comdex'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('core-1'))] = 'persistence'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('regen-1'))] = 'regen'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('umee-1'))] = 'umee'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('agoric-3'))] = 'agoric'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('dimension_37-1'))] = 'xpla'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('acre_9052-1'))] = 'acre'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('stride-1'))] = 'stride'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('carbon-1'))] = 'carbon'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('sommelier-3'))] = 'sommelier'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('neutron-1'))] = 'neutron'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('reb_1111-1'))] = 'rebus'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('archway-1'))] = 'archway'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('pio-mainnet-1'))] = 'provenance'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('ixo-5'))] = 'ixo'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('migaloo-1'))] = 'migaloo'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('teritori-1'))] = 'teritori'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('haqq_11235-1'))] = 'haqq'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('celestia'))] = 'celestia'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('agamotto'))] = 'ojo'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('chihuahua-1'))] = 'chihuahua'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('ssc-1'))] = 'saga'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('dymension_1100-1'))] = 'dymension'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('fxcore'))] = 'fxcore'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('perun-1'))] = 'c4e'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('bitsong-2b'))] = 'bitsong'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('pirin-1'))] = 'nolus'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('lava-mainnet-1'))] = 'lava'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('phoenix-1'))] = 'terra-2'; +// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('columbus-5'))] = 'terra';" diff --git a/contracts/crosschain/axelar/AxelarGatewayIncoming.sol b/contracts/crosschain/axelar/AxelarGatewayIncoming.sol new file mode 100644 index 00000000..b197a719 --- /dev/null +++ b/contracts/crosschain/axelar/AxelarGatewayIncoming.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {IAxelarGateway} from "../vendor/axelar/interfaces/IAxelarGateway.sol"; +import {IGatewayIncomingPassive} from "../IGatewayIncomingPassive.sol"; +import {IGatewayIncoming} from "../IGatewayIncoming.sol"; +import {IGatewayReceiver} from "../IGatewayReceiver.sol"; + +abstract contract AxelarGatewayIncoming is IGatewayIncoming, IGatewayIncomingPassive, IGatewayReceiver { + IAxelarGateway public immutable gateway; + + function validateReceivedMessage( + bytes32 messageId, + string calldata srcChain, // CAIP-2 chain ID + string calldata srcAccount, // i.e. address + bytes calldata payload, + bytes calldata attributes + ) public virtual { + if (!_isValidReceivedMessage(messageId, srcChain, srcAccount, payload, attributes)) { + revert GatewayIncomingPassiveInvalidMessage(messageId); + } + } + + function receiveMessage( + bytes32 messageId, + string calldata srcChain, + string calldata srcAccount, + bytes calldata payload, + bytes calldata attributes + ) external payable override { + if (msg.sender != address(gateway)) { + validateReceivedMessage(messageId, srcChain, srcAccount, payload, attributes); + } + emit MessageExecuted(messageId); + _execute(messageId, srcChain, srcAccount, payload, attributes); + } + + function _isValidReceivedMessage( + bytes32 messageId, + string calldata srcChain, // CAIP-2 chain ID + string calldata srcAccount, // i.e. address + bytes calldata payload, + bytes calldata /* attributes */ + ) internal returns (bool) { + return gateway.validateContractCall(messageId, srcChain, srcAccount, keccak256(payload)); + } + + function _execute( + bytes32 messageId, + string calldata srcChain, + string calldata srcAccount, + bytes calldata payload, + bytes calldata attributes + ) internal virtual; +} diff --git a/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol b/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol index c384d4d8..3ca52430 100644 --- a/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol +++ b/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol @@ -11,33 +11,32 @@ import {CAIP2} from "../../utils/CAIP-2.sol"; import {CAIP10} from "../../utils/CAIP-10.sol"; abstract contract AxelarGatewayOutgoing is IGatewayOutgoing, AxelarCAIP2Equivalence { - IAxelarGateway public immutable axelarGateway; + IAxelarGateway public immutable gateway; function sendMessage( string calldata destChain, // CAIP-2 chain ID string calldata destAccount, // i.e. address - bytes calldata data, + bytes calldata payload, bytes calldata attributes ) external payable override returns (bytes32 messageId) { // TODO: Handle ether (payable) // TODO: Validate attributes // Validate there's an equivalent chain identifier supported by the gateway - string memory destinationCAIP2 = CAIP2.toString(destChain); - if (!supported(destinationCAIP2)) revert UnsupportedChain(destinationCAIP2); + if (!supported(destChain)) revert UnsupportedChain(destChain); // Create a message Message memory message = Message( CAIP10.currentId(Strings.toHexString(msg.sender)), - CAIP10.toString(destinationCAIP2, destAccount), - data, + CAIP10.toString(destChain, destAccount), + payload, attributes ); bytes32 id = keccak256(message); emit MessageCreated(id, message); // Send the message - axelarGateway.callContract(string(fromCAIP2(destination)), destAccount, data); + gateway.callContract(string(fromCAIP2(destChain)), destAccount, message); emit MessageSent(id); return id; From 2f4588eb1e3e46269b9c983380466ec07ad84d3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 26 Aug 2024 16:50:32 -0600 Subject: [PATCH 16/51] Fix compilation --- contracts/crosschain/axelar/AxelarGatewayOutgoing.sol | 4 ++-- contracts/utils/CAIP-2.sol | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol b/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol index 3ca52430..ca96318e 100644 --- a/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol +++ b/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol @@ -32,11 +32,11 @@ abstract contract AxelarGatewayOutgoing is IGatewayOutgoing, AxelarCAIP2Equivale payload, attributes ); - bytes32 id = keccak256(message); + bytes32 id = keccak256(abi.encode(message)); emit MessageCreated(id, message); // Send the message - gateway.callContract(string(fromCAIP2(destChain)), destAccount, message); + gateway.callContract(string(fromCAIP2(destChain)), destAccount, payload); emit MessageSent(id); return id; diff --git a/contracts/utils/CAIP-2.sol b/contracts/utils/CAIP-2.sol index 3d0053c4..41d3249e 100644 --- a/contracts/utils/CAIP-2.sol +++ b/contracts/utils/CAIP-2.sol @@ -28,8 +28,8 @@ library CAIP2 { /// @dev Checks if the given CAIP2 identifier is the current chain. function isCurrentId(string memory caip2) internal view returns (bool) { (bytes8 namespace, bytes32 ref) = fromString(caip2); - (bytes8 currentNamespace, bytes32 currentRef) = currentId(); - return namespace == currentNamespace && ref == currentRef; + (bytes8 _namespace, bytes32 _ref) = currentId(); + return namespace == _namespace && ref == _ref; } /// @dev Returns the CAIP2 identifier of the current chain. From fd010bd57336628ad3eb47d83bef8e9586f7e41a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 26 Aug 2024 17:02:28 -0600 Subject: [PATCH 17/51] Apply review suggestions --- contracts/utils/Bytes.sol | 14 ++++++++++++++ contracts/utils/CAIP-10.sol | 21 +++++++++------------ contracts/utils/CAIP-2.sol | 22 +++++++--------------- 3 files changed, 30 insertions(+), 27 deletions(-) create mode 100644 contracts/utils/Bytes.sol diff --git a/contracts/utils/Bytes.sol b/contracts/utils/Bytes.sol new file mode 100644 index 00000000..ecd97f34 --- /dev/null +++ b/contracts/utils/Bytes.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +library Bytes { + function find(bytes memory input, bytes1 chr, uint256 cursor) internal pure returns (uint256) { + uint256 length = input.length; + for (uint256 i = cursor; i < length; ++i) { + if (input[i] == chr) { + return i; + } + } + return length; + } +} diff --git a/contracts/utils/CAIP-10.sol b/contracts/utils/CAIP-10.sol index 46e50c06..a30bf955 100644 --- a/contracts/utils/CAIP-10.sol +++ b/contracts/utils/CAIP-10.sol @@ -2,21 +2,27 @@ pragma solidity ^0.8.0; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {CAIP2} from "./CAIP-2.sol"; +import {Bytes} from "./Bytes.sol"; // account_id: chain_id + ":" + account_address // chain_id: [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See [CAIP-2][]) // account_address: [-.%a-zA-Z0-9]{1,128} library CAIP10 { + using SafeCast for uint256; + using Bytes for bytes; + bytes1 private constant SEMICOLON = ":"; function toString(string memory caip2, string memory accountId) internal pure returns (string memory) { return string(abi.encodePacked(caip2, SEMICOLON, accountId)); } - function fromString(string memory accountStr) internal pure returns (string memory caip2, string memory accountId) { - bytes memory accountBuffer = bytes(accountStr); - uint256 lastSeparatorIndex = _findLastSeparatorIndex(accountBuffer); + function parse(string memory caip10) internal pure returns (string memory caip2, string memory accountId) { + bytes memory accountBuffer = bytes(caip10); + uint8 firstSeparatorIndex = accountBuffer.find(SEMICOLON, 0).toUint8(); + uint256 lastSeparatorIndex = accountBuffer.find(SEMICOLON, firstSeparatorIndex).toUint8(); return (_extractCAIP2(accountBuffer, lastSeparatorIndex), _extractAccountId(accountBuffer, lastSeparatorIndex)); } @@ -48,13 +54,4 @@ library CAIP10 { } return string(_accountId); } - - function _findLastSeparatorIndex(bytes memory accountBuffer) private pure returns (uint256) { - for (uint256 i = accountBuffer.length - 1; i >= 0; i--) { - if (accountBuffer[i] == SEMICOLON) { - return i; - } - } - return 0; - } } diff --git a/contracts/utils/CAIP-2.sol b/contracts/utils/CAIP-2.sol index 41d3249e..558c6473 100644 --- a/contracts/utils/CAIP-2.sol +++ b/contracts/utils/CAIP-2.sol @@ -3,11 +3,15 @@ pragma solidity ^0.8.0; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import {Bytes} from "./Bytes.sol"; // chain_id: namespace + ":" + reference // namespace: [-a-z0-9]{3,8} // reference: [-_a-zA-Z0-9]{1,32} library CAIP2 { + using SafeCast for uint256; + using Bytes for bytes; + bytes16 private constant HEX_DIGITS = "0123456789abcdef"; bytes1 private constant SEMICOLON = ":"; bytes32 private constant EVM_REFERENCE = bytes32("eip155"); // EIP-155 for EVM chains @@ -19,15 +23,15 @@ library CAIP2 { /// @dev Parses a CAIP2 identifier from a string by splitting it at the first semicolon. /// The function parses both sides as `bytes8` and `bytes32` respectively without any validation. - function fromString(string memory caip2) internal pure returns (bytes8 namespace, bytes32 ref) { + function parse(string memory caip2) internal pure returns (bytes8 namespace, bytes32 ref) { bytes memory chainBuffer = bytes(caip2); - uint8 semicolonIndex = _findSemicolonIndex(chainBuffer); + uint8 semicolonIndex = chainBuffer.find(SEMICOLON, 0).toUint8(); return (_extractNamespace(chainBuffer, semicolonIndex), _unsafeExtractReference(chainBuffer, semicolonIndex)); } /// @dev Checks if the given CAIP2 identifier is the current chain. function isCurrentId(string memory caip2) internal view returns (bool) { - (bytes8 namespace, bytes32 ref) = fromString(caip2); + (bytes8 namespace, bytes32 ref) = parse(caip2); (bytes8 _namespace, bytes32 _ref) = currentId(); return namespace == _namespace && ref == _ref; } @@ -75,16 +79,4 @@ library CAIP2 { ref := mload(add(chainBuffer, add(0x20, offset))) } } - - /// @dev Looks for the first semicolon in the chain buffer. This is the optimal way since - /// the namespace is shorter than the reference. - function _findSemicolonIndex(bytes memory chainBuffer) private pure returns (uint8) { - uint8 length = SafeCast.toUint8(chainBuffer.length); - for (uint8 i = 0; i < length; i++) { - if (chainBuffer[i] == SEMICOLON) { - return i; - } - } - return length; - } } From aa731cc3f9dcc575e86e23f3279f3dee38edb65b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 2 Sep 2024 21:48:46 -0600 Subject: [PATCH 18/51] Install axelar contracts --- contracts/crosschain/ICAIP2Equivalence.sol | 4 +- .../axelar/AxelarGatewayIncoming.sol | 22 +- .../axelar/AxelarGatewayOutgoing.sol | 4 +- .../axelar/interfaces/IAxelarGasService.sol | 418 ------------------ .../axelar/interfaces/IAxelarGateway.sol | 188 -------- .../axelar/interfaces/IContractIdentifier.sol | 13 - .../vendor/axelar/interfaces/IGovernable.sol | 41 -- .../axelar/interfaces/IImplementation.sol | 11 - .../interfaces/IInterchainGasEstimation.sol | 45 -- .../vendor/axelar/interfaces/IOwnable.sol | 50 --- .../vendor/axelar/interfaces/IUpgradable.sol | 19 - .../axelar/types/GasEstimationTypes.sol | 35 -- package-lock.json | 22 + package.json | 1 + remappings.txt | 2 + 15 files changed, 34 insertions(+), 841 deletions(-) delete mode 100644 contracts/crosschain/vendor/axelar/interfaces/IAxelarGasService.sol delete mode 100644 contracts/crosschain/vendor/axelar/interfaces/IAxelarGateway.sol delete mode 100644 contracts/crosschain/vendor/axelar/interfaces/IContractIdentifier.sol delete mode 100644 contracts/crosschain/vendor/axelar/interfaces/IGovernable.sol delete mode 100644 contracts/crosschain/vendor/axelar/interfaces/IImplementation.sol delete mode 100644 contracts/crosschain/vendor/axelar/interfaces/IInterchainGasEstimation.sol delete mode 100644 contracts/crosschain/vendor/axelar/interfaces/IOwnable.sol delete mode 100644 contracts/crosschain/vendor/axelar/interfaces/IUpgradable.sol delete mode 100644 contracts/crosschain/vendor/axelar/types/GasEstimationTypes.sol diff --git a/contracts/crosschain/ICAIP2Equivalence.sol b/contracts/crosschain/ICAIP2Equivalence.sol index 68ac10af..b72dc9cb 100644 --- a/contracts/crosschain/ICAIP2Equivalence.sol +++ b/contracts/crosschain/ICAIP2Equivalence.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; -import {IAxelarGateway} from "./vendor/axelar/interfaces/IAxelarGateway.sol"; -import {IAxelarGasService} from "./vendor/axelar/interfaces/IAxelarGasService.sol"; +import {IAxelarGateway} from "@axelar-network/axelar-cgp-solidity/interfaces/IAxelarGateway.sol"; +import {IAxelarGasService} from "@axelar-network/axelar-cgp-solidity/interfaces/IAxelarGasService.sol"; /// @dev Equivalence interface between CAIP-2 chain identifiers and protocol-specific chain identifiers. /// diff --git a/contracts/crosschain/axelar/AxelarGatewayIncoming.sol b/contracts/crosschain/axelar/AxelarGatewayIncoming.sol index b197a719..71ee631a 100644 --- a/contracts/crosschain/axelar/AxelarGatewayIncoming.sol +++ b/contracts/crosschain/axelar/AxelarGatewayIncoming.sol @@ -2,12 +2,11 @@ pragma solidity ^0.8.0; -import {IAxelarGateway} from "../vendor/axelar/interfaces/IAxelarGateway.sol"; +import {IAxelarGateway} from "@axelar-network/axelar-cgp-solidity/interfaces/IAxelarGateway.sol"; import {IGatewayIncomingPassive} from "../IGatewayIncomingPassive.sol"; import {IGatewayIncoming} from "../IGatewayIncoming.sol"; -import {IGatewayReceiver} from "../IGatewayReceiver.sol"; -abstract contract AxelarGatewayIncoming is IGatewayIncoming, IGatewayIncomingPassive, IGatewayReceiver { +abstract contract AxelarGatewayIncoming is IGatewayIncoming, IGatewayIncomingPassive { IAxelarGateway public immutable gateway; function validateReceivedMessage( @@ -20,20 +19,7 @@ abstract contract AxelarGatewayIncoming is IGatewayIncoming, IGatewayIncomingPas if (!_isValidReceivedMessage(messageId, srcChain, srcAccount, payload, attributes)) { revert GatewayIncomingPassiveInvalidMessage(messageId); } - } - - function receiveMessage( - bytes32 messageId, - string calldata srcChain, - string calldata srcAccount, - bytes calldata payload, - bytes calldata attributes - ) external payable override { - if (msg.sender != address(gateway)) { - validateReceivedMessage(messageId, srcChain, srcAccount, payload, attributes); - } emit MessageExecuted(messageId); - _execute(messageId, srcChain, srcAccount, payload, attributes); } function _isValidReceivedMessage( @@ -52,5 +38,7 @@ abstract contract AxelarGatewayIncoming is IGatewayIncoming, IGatewayIncomingPas string calldata srcAccount, bytes calldata payload, bytes calldata attributes - ) internal virtual; + ) internal { + // messageId + } } diff --git a/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol b/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol index ca96318e..6cb2fb45 100644 --- a/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol +++ b/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.0; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {IAxelarGateway} from "../vendor/axelar/interfaces/IAxelarGateway.sol"; -import {IAxelarGasService} from "../vendor/axelar/interfaces/IAxelarGasService.sol"; +import {IAxelarGateway} from "@axelar-network/axelar-cgp-solidity/interfaces/IAxelarGateway.sol"; +import {IAxelarGasService} from "@axelar-network/axelar-cgp-solidity/interfaces/IAxelarGasService.sol"; import {IGatewayOutgoing} from "../IGatewayOutgoing.sol"; import {AxelarCAIP2Equivalence} from "./AxelarCAIP2Equivalence.sol"; import {CAIP2} from "../../utils/CAIP-2.sol"; diff --git a/contracts/crosschain/vendor/axelar/interfaces/IAxelarGasService.sol b/contracts/crosschain/vendor/axelar/interfaces/IAxelarGasService.sol deleted file mode 100644 index 60e363c2..00000000 --- a/contracts/crosschain/vendor/axelar/interfaces/IAxelarGasService.sol +++ /dev/null @@ -1,418 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {GasInfo} from "../types/GasEstimationTypes.sol"; -import {IInterchainGasEstimation} from "./IInterchainGasEstimation.sol"; -import {IUpgradable} from "./IUpgradable.sol"; - -/** - * @title IAxelarGasService Interface - * @notice This is an interface for the AxelarGasService contract which manages gas payments - * and refunds for cross-chain communication on the Axelar network. - * @dev This interface inherits IUpgradable - */ -interface IAxelarGasService is IInterchainGasEstimation, IUpgradable { - error InvalidAddress(); - error NotCollector(); - error InvalidAmounts(); - error InvalidGasUpdates(); - error InvalidParams(); - error InsufficientGasPayment(uint256 required, uint256 provided); - - event GasPaidForContractCall( - address indexed sourceAddress, - string destinationChain, - string destinationAddress, - bytes32 indexed payloadHash, - address gasToken, - uint256 gasFeeAmount, - address refundAddress - ); - - event GasPaidForContractCallWithToken( - address indexed sourceAddress, - string destinationChain, - string destinationAddress, - bytes32 indexed payloadHash, - string symbol, - uint256 amount, - address gasToken, - uint256 gasFeeAmount, - address refundAddress - ); - - event NativeGasPaidForContractCall( - address indexed sourceAddress, - string destinationChain, - string destinationAddress, - bytes32 indexed payloadHash, - uint256 gasFeeAmount, - address refundAddress - ); - - event NativeGasPaidForContractCallWithToken( - address indexed sourceAddress, - string destinationChain, - string destinationAddress, - bytes32 indexed payloadHash, - string symbol, - uint256 amount, - uint256 gasFeeAmount, - address refundAddress - ); - - event GasPaidForExpressCall( - address indexed sourceAddress, - string destinationChain, - string destinationAddress, - bytes32 indexed payloadHash, - address gasToken, - uint256 gasFeeAmount, - address refundAddress - ); - - event GasPaidForExpressCallWithToken( - address indexed sourceAddress, - string destinationChain, - string destinationAddress, - bytes32 indexed payloadHash, - string symbol, - uint256 amount, - address gasToken, - uint256 gasFeeAmount, - address refundAddress - ); - - event NativeGasPaidForExpressCall( - address indexed sourceAddress, - string destinationChain, - string destinationAddress, - bytes32 indexed payloadHash, - uint256 gasFeeAmount, - address refundAddress - ); - - event NativeGasPaidForExpressCallWithToken( - address indexed sourceAddress, - string destinationChain, - string destinationAddress, - bytes32 indexed payloadHash, - string symbol, - uint256 amount, - uint256 gasFeeAmount, - address refundAddress - ); - - event GasAdded( - bytes32 indexed txHash, - uint256 indexed logIndex, - address gasToken, - uint256 gasFeeAmount, - address refundAddress - ); - - event NativeGasAdded(bytes32 indexed txHash, uint256 indexed logIndex, uint256 gasFeeAmount, address refundAddress); - - event ExpressGasAdded( - bytes32 indexed txHash, - uint256 indexed logIndex, - address gasToken, - uint256 gasFeeAmount, - address refundAddress - ); - - event NativeExpressGasAdded( - bytes32 indexed txHash, - uint256 indexed logIndex, - uint256 gasFeeAmount, - address refundAddress - ); - - event Refunded( - bytes32 indexed txHash, - uint256 indexed logIndex, - address payable receiver, - address token, - uint256 amount - ); - - /** - * @notice Pay for gas for any type of contract execution on a destination chain. - * @dev This function is called on the source chain before calling the gateway to execute a remote contract. - * @dev If estimateOnChain is true, the function will estimate the gas cost and revert if the payment is insufficient. - * @param sender The address making the payment - * @param destinationChain The target chain where the contract call will be made - * @param destinationAddress The target address on the destination chain - * @param payload Data payload for the contract call - * @param executionGasLimit The gas limit for the contract call - * @param estimateOnChain Flag to enable on-chain gas estimation - * @param refundAddress The address where refunds, if any, should be sent - * @param params Additional parameters for gas payment. This can be left empty for normal contract call payments. - */ - function payGas( - address sender, - string calldata destinationChain, - string calldata destinationAddress, - bytes calldata payload, - uint256 executionGasLimit, - bool estimateOnChain, - address refundAddress, - bytes calldata params - ) external payable; - - /** - * @notice Pay for gas using ERC20 tokens for a contract call on a destination chain. - * @dev This function is called on the source chain before calling the gateway to execute a remote contract. - * @param sender The address making the payment - * @param destinationChain The target chain where the contract call will be made - * @param destinationAddress The target address on the destination chain - * @param payload Data payload for the contract call - * @param gasToken The address of the ERC20 token used to pay for gas - * @param gasFeeAmount The amount of tokens to pay for gas - * @param refundAddress The address where refunds, if any, should be sent - */ - function payGasForContractCall( - address sender, - string calldata destinationChain, - string calldata destinationAddress, - bytes calldata payload, - address gasToken, - uint256 gasFeeAmount, - address refundAddress - ) external; - - /** - * @notice Pay for gas using ERC20 tokens for a contract call with tokens on a destination chain. - * @dev This function is called on the source chain before calling the gateway to execute a remote contract. - * @param sender The address making the payment - * @param destinationChain The target chain where the contract call with tokens will be made - * @param destinationAddress The target address on the destination chain - * @param payload Data payload for the contract call with tokens - * @param symbol The symbol of the token to be sent with the call - * @param amount The amount of tokens to be sent with the call - * @param gasToken The address of the ERC20 token used to pay for gas - * @param gasFeeAmount The amount of tokens to pay for gas - * @param refundAddress The address where refunds, if any, should be sent - */ - function payGasForContractCallWithToken( - address sender, - string calldata destinationChain, - string calldata destinationAddress, - bytes calldata payload, - string calldata symbol, - uint256 amount, - address gasToken, - uint256 gasFeeAmount, - address refundAddress - ) external; - - /** - * @notice Pay for gas using native currency for a contract call on a destination chain. - * @dev This function is called on the source chain before calling the gateway to execute a remote contract. - * @param sender The address making the payment - * @param destinationChain The target chain where the contract call will be made - * @param destinationAddress The target address on the destination chain - * @param payload Data payload for the contract call - * @param refundAddress The address where refunds, if any, should be sent - */ - function payNativeGasForContractCall( - address sender, - string calldata destinationChain, - string calldata destinationAddress, - bytes calldata payload, - address refundAddress - ) external payable; - - /** - * @notice Pay for gas using native currency for a contract call with tokens on a destination chain. - * @dev This function is called on the source chain before calling the gateway to execute a remote contract. - * @param sender The address making the payment - * @param destinationChain The target chain where the contract call with tokens will be made - * @param destinationAddress The target address on the destination chain - * @param payload Data payload for the contract call with tokens - * @param symbol The symbol of the token to be sent with the call - * @param amount The amount of tokens to be sent with the call - * @param refundAddress The address where refunds, if any, should be sent - */ - function payNativeGasForContractCallWithToken( - address sender, - string calldata destinationChain, - string calldata destinationAddress, - bytes calldata payload, - string calldata symbol, - uint256 amount, - address refundAddress - ) external payable; - - /** - * @notice Pay for gas using ERC20 tokens for an express contract call on a destination chain. - * @dev This function is called on the source chain before calling the gateway to express execute a remote contract. - * @param sender The address making the payment - * @param destinationChain The target chain where the contract call will be made - * @param destinationAddress The target address on the destination chain - * @param payload Data payload for the contract call - * @param gasToken The address of the ERC20 token used to pay for gas - * @param gasFeeAmount The amount of tokens to pay for gas - * @param refundAddress The address where refunds, if any, should be sent - */ - function payGasForExpressCall( - address sender, - string calldata destinationChain, - string calldata destinationAddress, - bytes calldata payload, - address gasToken, - uint256 gasFeeAmount, - address refundAddress - ) external; - - /** - * @notice Pay for gas using ERC20 tokens for an express contract call with tokens on a destination chain. - * @dev This function is called on the source chain before calling the gateway to express execute a remote contract. - * @param sender The address making the payment - * @param destinationChain The target chain where the contract call with tokens will be made - * @param destinationAddress The target address on the destination chain - * @param payload Data payload for the contract call with tokens - * @param symbol The symbol of the token to be sent with the call - * @param amount The amount of tokens to be sent with the call - * @param gasToken The address of the ERC20 token used to pay for gas - * @param gasFeeAmount The amount of tokens to pay for gas - * @param refundAddress The address where refunds, if any, should be sent - */ - function payGasForExpressCallWithToken( - address sender, - string calldata destinationChain, - string calldata destinationAddress, - bytes calldata payload, - string calldata symbol, - uint256 amount, - address gasToken, - uint256 gasFeeAmount, - address refundAddress - ) external; - - /** - * @notice Pay for gas using native currency for an express contract call on a destination chain. - * @dev This function is called on the source chain before calling the gateway to execute a remote contract. - * @param sender The address making the payment - * @param destinationChain The target chain where the contract call will be made - * @param destinationAddress The target address on the destination chain - * @param payload Data payload for the contract call - * @param refundAddress The address where refunds, if any, should be sent - */ - function payNativeGasForExpressCall( - address sender, - string calldata destinationChain, - string calldata destinationAddress, - bytes calldata payload, - address refundAddress - ) external payable; - - /** - * @notice Pay for gas using native currency for an express contract call with tokens on a destination chain. - * @dev This function is called on the source chain before calling the gateway to execute a remote contract. - * @param sender The address making the payment - * @param destinationChain The target chain where the contract call with tokens will be made - * @param destinationAddress The target address on the destination chain - * @param payload Data payload for the contract call with tokens - * @param symbol The symbol of the token to be sent with the call - * @param amount The amount of tokens to be sent with the call - * @param refundAddress The address where refunds, if any, should be sent - */ - function payNativeGasForExpressCallWithToken( - address sender, - string calldata destinationChain, - string calldata destinationAddress, - bytes calldata payload, - string calldata symbol, - uint256 amount, - address refundAddress - ) external payable; - - /** - * @notice Add additional gas payment using ERC20 tokens after initiating a cross-chain call. - * @dev This function can be called on the source chain after calling the gateway to execute a remote contract. - * @param txHash The transaction hash of the cross-chain call - * @param logIndex The log index for the cross-chain call - * @param gasToken The ERC20 token address used to add gas - * @param gasFeeAmount The amount of tokens to add as gas - * @param refundAddress The address where refunds, if any, should be sent - */ - function addGas( - bytes32 txHash, - uint256 logIndex, - address gasToken, - uint256 gasFeeAmount, - address refundAddress - ) external; - - /** - * @notice Add additional gas payment using native currency after initiating a cross-chain call. - * @dev This function can be called on the source chain after calling the gateway to execute a remote contract. - * @param txHash The transaction hash of the cross-chain call - * @param logIndex The log index for the cross-chain call - * @param refundAddress The address where refunds, if any, should be sent - */ - function addNativeGas(bytes32 txHash, uint256 logIndex, address refundAddress) external payable; - - /** - * @notice Add additional gas payment using ERC20 tokens after initiating an express cross-chain call. - * @dev This function can be called on the source chain after calling the gateway to express execute a remote contract. - * @param txHash The transaction hash of the cross-chain call - * @param logIndex The log index for the cross-chain call - * @param gasToken The ERC20 token address used to add gas - * @param gasFeeAmount The amount of tokens to add as gas - * @param refundAddress The address where refunds, if any, should be sent - */ - function addExpressGas( - bytes32 txHash, - uint256 logIndex, - address gasToken, - uint256 gasFeeAmount, - address refundAddress - ) external; - - /** - * @notice Add additional gas payment using native currency after initiating an express cross-chain call. - * @dev This function can be called on the source chain after calling the gateway to express execute a remote contract. - * @param txHash The transaction hash of the cross-chain call - * @param logIndex The log index for the cross-chain call - * @param refundAddress The address where refunds, if any, should be sent - */ - function addNativeExpressGas(bytes32 txHash, uint256 logIndex, address refundAddress) external payable; - - /** - * @notice Updates the gas price for a specific chain. - * @dev This function is called by the gas oracle to update the gas prices for a specific chains. - * @param chains Array of chain names - * @param gasUpdates Array of gas updates - */ - function updateGasInfo(string[] calldata chains, GasInfo[] calldata gasUpdates) external; - - /** - * @notice Allows the gasCollector to collect accumulated fees from the contract. - * @dev Use address(0) as the token address for native currency. - * @param receiver The address to receive the collected fees - * @param tokens Array of token addresses to be collected - * @param amounts Array of amounts to be collected for each respective token address - */ - function collectFees(address payable receiver, address[] calldata tokens, uint256[] calldata amounts) external; - - /** - * @notice Refunds gas payment to the receiver in relation to a specific cross-chain transaction. - * @dev Only callable by the gasCollector. - * @dev Use address(0) as the token address to refund native currency. - * @param txHash The transaction hash of the cross-chain call - * @param logIndex The log index for the cross-chain call - * @param receiver The address to receive the refund - * @param token The token address to be refunded - * @param amount The amount to refund - */ - function refund(bytes32 txHash, uint256 logIndex, address payable receiver, address token, uint256 amount) external; - - /** - * @notice Returns the address of the designated gas collector. - * @return address of the gas collector - */ - function gasCollector() external returns (address); -} diff --git a/contracts/crosschain/vendor/axelar/interfaces/IAxelarGateway.sol b/contracts/crosschain/vendor/axelar/interfaces/IAxelarGateway.sol deleted file mode 100644 index bb12c951..00000000 --- a/contracts/crosschain/vendor/axelar/interfaces/IAxelarGateway.sol +++ /dev/null @@ -1,188 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {IGovernable} from "./IGovernable.sol"; -import {IImplementation} from "./IImplementation.sol"; - -interface IAxelarGateway is IImplementation, IGovernable { - /**********\ - |* Errors *| - \**********/ - - error NotSelf(); - error InvalidCodeHash(); - error SetupFailed(); - error InvalidAuthModule(); - error InvalidTokenDeployer(); - error InvalidAmount(); - error InvalidChainId(); - error InvalidCommands(); - error TokenDoesNotExist(string symbol); - error TokenAlreadyExists(string symbol); - error TokenDeployFailed(string symbol); - error TokenContractDoesNotExist(address token); - error BurnFailed(string symbol); - error MintFailed(string symbol); - error InvalidSetMintLimitsParams(); - error ExceedMintLimit(string symbol); - - /**********\ - |* Events *| - \**********/ - - event TokenSent( - address indexed sender, - string destinationChain, - string destinationAddress, - string symbol, - uint256 amount - ); - - event ContractCall( - address indexed sender, - string destinationChain, - string destinationContractAddress, - bytes32 indexed payloadHash, - bytes payload - ); - - event ContractCallWithToken( - address indexed sender, - string destinationChain, - string destinationContractAddress, - bytes32 indexed payloadHash, - bytes payload, - string symbol, - uint256 amount - ); - - event Executed(bytes32 indexed commandId); - - event TokenDeployed(string symbol, address tokenAddresses); - - event ContractCallApproved( - bytes32 indexed commandId, - string sourceChain, - string sourceAddress, - address indexed contractAddress, - bytes32 indexed payloadHash, - bytes32 sourceTxHash, - uint256 sourceEventIndex - ); - - event ContractCallApprovedWithMint( - bytes32 indexed commandId, - string sourceChain, - string sourceAddress, - address indexed contractAddress, - bytes32 indexed payloadHash, - string symbol, - uint256 amount, - bytes32 sourceTxHash, - uint256 sourceEventIndex - ); - - event ContractCallExecuted(bytes32 indexed commandId); - - event TokenMintLimitUpdated(string symbol, uint256 limit); - - event OperatorshipTransferred(bytes newOperatorsData); - - event Upgraded(address indexed implementation); - - /********************\ - |* Public Functions *| - \********************/ - - function sendToken( - string calldata destinationChain, - string calldata destinationAddress, - string calldata symbol, - uint256 amount - ) external; - - function callContract( - string calldata destinationChain, - string calldata contractAddress, - bytes calldata payload - ) external; - - function callContractWithToken( - string calldata destinationChain, - string calldata contractAddress, - bytes calldata payload, - string calldata symbol, - uint256 amount - ) external; - - function isContractCallApproved( - bytes32 commandId, - string calldata sourceChain, - string calldata sourceAddress, - address contractAddress, - bytes32 payloadHash - ) external view returns (bool); - - function isContractCallAndMintApproved( - bytes32 commandId, - string calldata sourceChain, - string calldata sourceAddress, - address contractAddress, - bytes32 payloadHash, - string calldata symbol, - uint256 amount - ) external view returns (bool); - - function validateContractCall( - bytes32 commandId, - string calldata sourceChain, - string calldata sourceAddress, - bytes32 payloadHash - ) external returns (bool); - - function validateContractCallAndMint( - bytes32 commandId, - string calldata sourceChain, - string calldata sourceAddress, - bytes32 payloadHash, - string calldata symbol, - uint256 amount - ) external returns (bool); - - /***********\ - |* Getters *| - \***********/ - - function authModule() external view returns (address); - - function tokenDeployer() external view returns (address); - - function tokenMintLimit(string memory symbol) external view returns (uint256); - - function tokenMintAmount(string memory symbol) external view returns (uint256); - - function allTokensFrozen() external view returns (bool); - - function implementation() external view returns (address); - - function tokenAddresses(string memory symbol) external view returns (address); - - function tokenFrozen(string memory symbol) external view returns (bool); - - function isCommandExecuted(bytes32 commandId) external view returns (bool); - - /************************\ - |* Governance Functions *| - \************************/ - - function setTokenMintLimits(string[] calldata symbols, uint256[] calldata limits) external; - - function upgrade(address newImplementation, bytes32 newImplementationCodeHash, bytes calldata setupParams) external; - - /**********************\ - |* External Functions *| - \**********************/ - - function execute(bytes calldata input) external; -} diff --git a/contracts/crosschain/vendor/axelar/interfaces/IContractIdentifier.sol b/contracts/crosschain/vendor/axelar/interfaces/IContractIdentifier.sol deleted file mode 100644 index bf32f96c..00000000 --- a/contracts/crosschain/vendor/axelar/interfaces/IContractIdentifier.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -// General interface for upgradable contracts -interface IContractIdentifier { - /** - * @notice Returns the contract ID. It can be used as a check during upgrades. - * @dev Meant to be overridden in derived contracts. - * @return bytes32 The contract ID - */ - function contractId() external pure returns (bytes32); -} diff --git a/contracts/crosschain/vendor/axelar/interfaces/IGovernable.sol b/contracts/crosschain/vendor/axelar/interfaces/IGovernable.sol deleted file mode 100644 index c08f4afc..00000000 --- a/contracts/crosschain/vendor/axelar/interfaces/IGovernable.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -/** - * @title IGovernable Interface - * @notice This is an interface used by the AxelarGateway contract to manage governance and mint limiter roles. - */ -interface IGovernable { - error NotGovernance(); - error NotMintLimiter(); - error InvalidGovernance(); - error InvalidMintLimiter(); - - event GovernanceTransferred(address indexed previousGovernance, address indexed newGovernance); - event MintLimiterTransferred(address indexed previousGovernance, address indexed newGovernance); - - /** - * @notice Returns the governance address. - * @return address of the governance - */ - function governance() external view returns (address); - - /** - * @notice Returns the mint limiter address. - * @return address of the mint limiter - */ - function mintLimiter() external view returns (address); - - /** - * @notice Transfer the governance role to another address. - * @param newGovernance The new governance address - */ - function transferGovernance(address newGovernance) external; - - /** - * @notice Transfer the mint limiter role to another address. - * @param newGovernance The new mint limiter address - */ - function transferMintLimiter(address newGovernance) external; -} diff --git a/contracts/crosschain/vendor/axelar/interfaces/IImplementation.sol b/contracts/crosschain/vendor/axelar/interfaces/IImplementation.sol deleted file mode 100644 index ef2631d4..00000000 --- a/contracts/crosschain/vendor/axelar/interfaces/IImplementation.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {IContractIdentifier} from "./IContractIdentifier.sol"; - -interface IImplementation is IContractIdentifier { - error NotProxy(); - - function setup(bytes calldata data) external; -} diff --git a/contracts/crosschain/vendor/axelar/interfaces/IInterchainGasEstimation.sol b/contracts/crosschain/vendor/axelar/interfaces/IInterchainGasEstimation.sol deleted file mode 100644 index 74d4cce8..00000000 --- a/contracts/crosschain/vendor/axelar/interfaces/IInterchainGasEstimation.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {GasEstimationType, GasInfo} from "../types/GasEstimationTypes.sol"; - -/** - * @title IInterchainGasEstimation Interface - * @notice This is an interface for the InterchainGasEstimation contract - * which allows for estimating gas fees for cross-chain communication on the Axelar network. - */ -interface IInterchainGasEstimation { - error UnsupportedEstimationType(GasEstimationType gasEstimationType); - - /** - * @notice Event emitted when the gas price for a specific chain is updated. - * @param chain The name of the chain - * @param info The gas info for the chain - */ - event GasInfoUpdated(string chain, GasInfo info); - - /** - * @notice Returns the gas price for a specific chain. - * @param chain The name of the chain - * @return gasInfo The gas info for the chain - */ - function getGasInfo(string calldata chain) external view returns (GasInfo memory); - - /** - * @notice Estimates the gas fee for a cross-chain contract call. - * @param destinationChain Axelar registered name of the destination chain - * @param destinationAddress Destination contract address being called - * @param executionGasLimit The gas limit to be used for the destination contract execution, - * e.g. pass in 200k if your app consumes needs upto 200k for this contract call - * @param params Additional parameters for the gas estimation - * @return gasEstimate The cross-chain gas estimate, in terms of source chain's native gas token that should be forwarded to the gas service. - */ - function estimateGasFee( - string calldata destinationChain, - string calldata destinationAddress, - bytes calldata payload, - uint256 executionGasLimit, - bytes calldata params - ) external view returns (uint256 gasEstimate); -} diff --git a/contracts/crosschain/vendor/axelar/interfaces/IOwnable.sol b/contracts/crosschain/vendor/axelar/interfaces/IOwnable.sol deleted file mode 100644 index 725bcb36..00000000 --- a/contracts/crosschain/vendor/axelar/interfaces/IOwnable.sol +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -/** - * @title IOwnable Interface - * @notice IOwnable is an interface that abstracts the implementation of a - * contract with ownership control features. It's commonly used in upgradable - * contracts and includes the functionality to get current owner, transfer - * ownership, and propose and accept ownership. - */ -interface IOwnable { - error NotOwner(); - error InvalidOwner(); - error InvalidOwnerAddress(); - - event OwnershipTransferStarted(address indexed newOwner); - event OwnershipTransferred(address indexed newOwner); - - /** - * @notice Returns the current owner of the contract. - * @return address The address of the current owner - */ - function owner() external view returns (address); - - /** - * @notice Returns the address of the pending owner of the contract. - * @return address The address of the pending owner - */ - function pendingOwner() external view returns (address); - - /** - * @notice Transfers ownership of the contract to a new address - * @param newOwner The address to transfer ownership to - */ - function transferOwnership(address newOwner) external; - - /** - * @notice Proposes to transfer the contract's ownership to a new address. - * The new owner needs to accept the ownership explicitly. - * @param newOwner The address to transfer ownership to - */ - function proposeOwnership(address newOwner) external; - - /** - * @notice Transfers ownership to the pending owner. - * @dev Can only be called by the pending owner - */ - function acceptOwnership() external; -} diff --git a/contracts/crosschain/vendor/axelar/interfaces/IUpgradable.sol b/contracts/crosschain/vendor/axelar/interfaces/IUpgradable.sol deleted file mode 100644 index c7a14709..00000000 --- a/contracts/crosschain/vendor/axelar/interfaces/IUpgradable.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {IOwnable} from "./IOwnable.sol"; -import {IImplementation} from "./IImplementation.sol"; - -// General interface for upgradable contracts -interface IUpgradable is IOwnable, IImplementation { - error InvalidCodeHash(); - error InvalidImplementation(); - error SetupFailed(); - - event Upgraded(address indexed newImplementation); - - function implementation() external view returns (address); - - function upgrade(address newImplementation, bytes32 newImplementationCodeHash, bytes calldata params) external; -} diff --git a/contracts/crosschain/vendor/axelar/types/GasEstimationTypes.sol b/contracts/crosschain/vendor/axelar/types/GasEstimationTypes.sol deleted file mode 100644 index 7a3aa14d..00000000 --- a/contracts/crosschain/vendor/axelar/types/GasEstimationTypes.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -/** - * @title GasEstimationType - * @notice This enum represents the gas estimation types for different chains. - */ -enum GasEstimationType { - Default, - OptimismEcotone, - OptimismBedrock, - Arbitrum, - Scroll -} - -/** - * @title GasInfo - * @notice This struct represents the gas pricing information for a specific chain. - * @dev Smaller uint types are used for efficient struct packing to save storage costs. - */ -struct GasInfo { - /// @dev Custom gas pricing rule, such as L1 data fee on L2s - uint64 gasEstimationType; - /// @dev Scalar value needed for specific gas estimation types, expected to be less than 1e10 - uint64 l1FeeScalar; - /// @dev Axelar base fee for cross-chain message approval on destination, in terms of source native gas token - uint128 axelarBaseFee; - /// @dev Gas price of destination chain, in terms of the source chain token, i.e dest_gas_price * dest_token_market_price / src_token_market_price - uint128 relativeGasPrice; - /// @dev Needed for specific gas estimation types. Blob base fee of destination chain, in terms of the source chain token, i.e dest_blob_base_fee * dest_token_market_price / src_token_market_price - uint128 relativeBlobBaseFee; - /// @dev Axelar express fee for express execution, in terms of source chain token - uint128 expressFee; -} diff --git a/package-lock.json b/package-lock.json index 0f2bf1ea..de94f67b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "license": "MIT", "dependencies": { + "@axelar-network/axelar-cgp-solidity": "^6.3.1", "@openzeppelin/contracts": "^5.0.2", "@openzeppelin/contracts-upgradeable": "^5.0.2" }, @@ -29,6 +30,27 @@ "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", "dev": true }, + "node_modules/@axelar-network/axelar-cgp-solidity": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@axelar-network/axelar-cgp-solidity/-/axelar-cgp-solidity-6.3.1.tgz", + "integrity": "sha512-RJmcOQbj2VhQb8uqBtu0beNj4MKRVjiYj74wXu6QI/a4bJ5EwwZK3+uCbTVNM9Qc7LqJqObhtGQfKUnAXEFCHA==", + "license": "MIT", + "dependencies": { + "@axelar-network/axelar-gmp-sdk-solidity": "5.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@axelar-network/axelar-gmp-sdk-solidity": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@axelar-network/axelar-gmp-sdk-solidity/-/axelar-gmp-sdk-solidity-5.8.0.tgz", + "integrity": "sha512-ThiCWK7lhwmsipgjKkw8c0z0ubB9toRMV9X0tRVOXHHSknKp5DCFfatbCwjpSC5GZRa+61ciTSqJNtCc7j9YoQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@ethersproject/abi": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", diff --git a/package.json b/package.json index 1cddf635..e2fea6fb 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "zeppelin" ], "dependencies": { + "@axelar-network/axelar-cgp-solidity": "^6.3.1", "@openzeppelin/contracts": "^5.0.2", "@openzeppelin/contracts-upgradeable": "^5.0.2" }, diff --git a/remappings.txt b/remappings.txt index 0097828b..975a565c 100644 --- a/remappings.txt +++ b/remappings.txt @@ -3,3 +3,5 @@ @openzeppelin/contracts@master/=lib/@openzeppelin-contracts/contracts/ @openzeppelin/contracts-upgradeable@master/=lib/@openzeppelin-contracts-upgradeable/contracts/ + +@axelar-network/axelar-cgp-solidity/=node_modules/@axelar-network/axelar-cgp-solidity/contracts/ From e39ff3a675e0beff6fbfcc995cb8e32d83694e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 2 Sep 2024 21:48:58 -0600 Subject: [PATCH 19/51] Apply review sugggestion --- contracts/utils/CAIP-10.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/utils/CAIP-10.sol b/contracts/utils/CAIP-10.sol index a30bf955..763195e3 100644 --- a/contracts/utils/CAIP-10.sol +++ b/contracts/utils/CAIP-10.sol @@ -13,16 +13,16 @@ library CAIP10 { using SafeCast for uint256; using Bytes for bytes; - bytes1 private constant SEMICOLON = ":"; + bytes1 private constant COLON = ":"; function toString(string memory caip2, string memory accountId) internal pure returns (string memory) { - return string(abi.encodePacked(caip2, SEMICOLON, accountId)); + return string(abi.encodePacked(caip2, COLON, accountId)); } function parse(string memory caip10) internal pure returns (string memory caip2, string memory accountId) { bytes memory accountBuffer = bytes(caip10); - uint8 firstSeparatorIndex = accountBuffer.find(SEMICOLON, 0).toUint8(); - uint256 lastSeparatorIndex = accountBuffer.find(SEMICOLON, firstSeparatorIndex).toUint8(); + uint8 firstSeparatorIndex = accountBuffer.find(COLON, 0).toUint8(); + uint256 lastSeparatorIndex = accountBuffer.find(COLON, firstSeparatorIndex).toUint8(); return (_extractCAIP2(accountBuffer, lastSeparatorIndex), _extractAccountId(accountBuffer, lastSeparatorIndex)); } From 0e7d04049a77ce0fcbc3de5e4266ee0921b91122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 2 Sep 2024 22:14:32 -0600 Subject: [PATCH 20/51] Apply suggestions --- .../axelar/AxelarGatewayIncoming.sol | 26 ++++++++++--------- package-lock.json | 14 +++++++++- package.json | 1 + remappings.txt | 1 + 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/contracts/crosschain/axelar/AxelarGatewayIncoming.sol b/contracts/crosschain/axelar/AxelarGatewayIncoming.sol index 71ee631a..cb96d990 100644 --- a/contracts/crosschain/axelar/AxelarGatewayIncoming.sol +++ b/contracts/crosschain/axelar/AxelarGatewayIncoming.sol @@ -5,10 +5,16 @@ pragma solidity ^0.8.0; import {IAxelarGateway} from "@axelar-network/axelar-cgp-solidity/interfaces/IAxelarGateway.sol"; import {IGatewayIncomingPassive} from "../IGatewayIncomingPassive.sol"; import {IGatewayIncoming} from "../IGatewayIncoming.sol"; +import {IGatewayReceiver} from "../IGatewayReceiver.sol"; +import {AxelarCAIP2Equivalence} from "./AxelarCAIP2Equivalence.sol"; +import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/executable/AxelarExecutable.sol"; -abstract contract AxelarGatewayIncoming is IGatewayIncoming, IGatewayIncomingPassive { - IAxelarGateway public immutable gateway; - +abstract contract AxelarGatewayIncoming is + AxelarExecutable, + AxelarCAIP2Equivalence, + IGatewayIncoming, + IGatewayIncomingPassive +{ function validateReceivedMessage( bytes32 messageId, string calldata srcChain, // CAIP-2 chain ID @@ -19,7 +25,7 @@ abstract contract AxelarGatewayIncoming is IGatewayIncoming, IGatewayIncomingPas if (!_isValidReceivedMessage(messageId, srcChain, srcAccount, payload, attributes)) { revert GatewayIncomingPassiveInvalidMessage(messageId); } - emit MessageExecuted(messageId); + _execute(string(fromCAIP2(destChain)), srcAccount, payload); } function _isValidReceivedMessage( @@ -32,13 +38,9 @@ abstract contract AxelarGatewayIncoming is IGatewayIncoming, IGatewayIncomingPas return gateway.validateContractCall(messageId, srcChain, srcAccount, keccak256(payload)); } - function _execute( - bytes32 messageId, - string calldata srcChain, - string calldata srcAccount, - bytes calldata payload, - bytes calldata attributes - ) internal { - // messageId + function _execute(string calldata srcChain, string calldata srcAccount, bytes calldata payload) internal virtual { + (address destination, bytes memory data) = abi.decode(payload, (address, bytes)); + IGatewayReceiver(destination).receiveMessage(keccak256(data), sourceChain, sourceAddress, data, ""); + emit MessageExecuted(keccak256(data)); // What to use if we can't reconstruct the message? } } diff --git a/package-lock.json b/package-lock.json index b8e72f4a..0e196841 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@axelar-network/axelar-cgp-solidity": "^6.3.1", + "@axelar-network/axelar-gmp-sdk-solidity": "^5.10.0", "@openzeppelin/contracts": "^5.0.2", "@openzeppelin/contracts-upgradeable": "^5.0.2" }, @@ -42,8 +43,19 @@ "node": ">=18" } }, - "node_modules/@axelar-network/axelar-gmp-sdk-solidity": { + "node_modules/@axelar-network/axelar-cgp-solidity/node_modules/@axelar-network/axelar-gmp-sdk-solidity": { "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@axelar-network/axelar-gmp-sdk-solidity/-/axelar-gmp-sdk-solidity-5.8.0.tgz", + "integrity": "sha512-ThiCWK7lhwmsipgjKkw8c0z0ubB9toRMV9X0tRVOXHHSknKp5DCFfatbCwjpSC5GZRa+61ciTSqJNtCc7j9YoQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@axelar-network/axelar-gmp-sdk-solidity": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@axelar-network/axelar-gmp-sdk-solidity/-/axelar-gmp-sdk-solidity-5.10.0.tgz", + "integrity": "sha512-s8SImALvYB+5AeiT3tbfWNBI2Mhqw1x91i/zM3DNpVUCnAR2HKtsB9T84KnUn/OJjOVgb4h0lv7q9smeYniRPw==", "license": "MIT", "engines": { "node": ">=18" diff --git a/package.json b/package.json index 3d7a26f4..02b8f1aa 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ ], "dependencies": { "@axelar-network/axelar-cgp-solidity": "^6.3.1", + "@axelar-network/axelar-gmp-sdk-solidity": "^5.10.0", "@openzeppelin/contracts": "^5.0.2", "@openzeppelin/contracts-upgradeable": "^5.0.2" }, diff --git a/remappings.txt b/remappings.txt index 71bac809..138ddb98 100644 --- a/remappings.txt +++ b/remappings.txt @@ -7,3 +7,4 @@ @openzeppelin/community-contracts/=contracts/ @axelar-network/axelar-cgp-solidity/=node_modules/@axelar-network/axelar-cgp-solidity/contracts/ +@axelar-network/axelar-gmp-sdk-solidity/=node_modules/@axelar-network/axelar-gmp-sdk-solidity/contracts/ From 24029264ee1618ab57dc43318b08290540833fe7 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Tue, 3 Sep 2024 12:43:54 -0300 Subject: [PATCH 21/51] wip fixes --- .../axelar/AxelarGatewayIncoming.sol | 22 +++++++++++-------- .../axelar/AxelarGatewayOutgoing.sol | 12 +++++----- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/contracts/crosschain/axelar/AxelarGatewayIncoming.sol b/contracts/crosschain/axelar/AxelarGatewayIncoming.sol index cb96d990..d0c8ec74 100644 --- a/contracts/crosschain/axelar/AxelarGatewayIncoming.sol +++ b/contracts/crosschain/axelar/AxelarGatewayIncoming.sol @@ -8,6 +8,7 @@ import {IGatewayIncoming} from "../IGatewayIncoming.sol"; import {IGatewayReceiver} from "../IGatewayReceiver.sol"; import {AxelarCAIP2Equivalence} from "./AxelarCAIP2Equivalence.sol"; import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/executable/AxelarExecutable.sol"; +import {CAIP10} from "../../utils/CAIP-10.sol"; abstract contract AxelarGatewayIncoming is AxelarExecutable, @@ -22,25 +23,28 @@ abstract contract AxelarGatewayIncoming is bytes calldata payload, bytes calldata attributes ) public virtual { - if (!_isValidReceivedMessage(messageId, srcChain, srcAccount, payload, attributes)) { + address dstAccount = CAIP10.toString(msg.sender); + if (!_isValidReceivedMessage(messageId, srcChain, srcAccount, dstAccount, msg.sender, paylod, attributes)) { revert GatewayIncomingPassiveInvalidMessage(messageId); } - _execute(string(fromCAIP2(destChain)), srcAccount, payload); + _execute(string(fromCAIP2(destChain)), srcAccount, wrappedPayload); } function _isValidReceivedMessage( bytes32 messageId, string calldata srcChain, // CAIP-2 chain ID string calldata srcAccount, // i.e. address - bytes calldata payload, - bytes calldata /* attributes */ + string calldata dstAccount, + bytes calldata paylod, + bytes calldata attributes ) internal returns (bool) { - return gateway.validateContractCall(messageId, srcChain, srcAccount, keccak256(payload)); + bytes wrappedPayload = abi.encode(messageId, dstAccount, payload, attributes); + return gateway.validateContractCall(messageId, srcChain, srcAccount, keccak256(wrappedPayload)); } - function _execute(string calldata srcChain, string calldata srcAccount, bytes calldata payload) internal virtual { - (address destination, bytes memory data) = abi.decode(payload, (address, bytes)); - IGatewayReceiver(destination).receiveMessage(keccak256(data), sourceChain, sourceAddress, data, ""); - emit MessageExecuted(keccak256(data)); // What to use if we can't reconstruct the message? + function _execute(string calldata srcChain, string calldata srcAccount, bytes calldata wrappedPayload) internal virtual { + (bytes32 messageId, string destAccount, bytes payload, bytes attributes) = abi.decode(wrappedPayload, (bytes32, string, bytes, bytes)); + IGatewayReceiver(destination).receiveMessage(messageId, srcChain, srcAccount, payload, attributes); + emit MessageExecuted(messageId); } } diff --git a/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol b/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol index 6cb2fb45..02531683 100644 --- a/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol +++ b/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol @@ -32,13 +32,15 @@ abstract contract AxelarGatewayOutgoing is IGatewayOutgoing, AxelarCAIP2Equivale payload, attributes ); - bytes32 id = keccak256(abi.encode(message)); + messageId = keccak256(abi.encode(message)); emit MessageCreated(id, message); - // Send the message - gateway.callContract(string(fromCAIP2(destChain)), destAccount, payload); - emit MessageSent(id); + // Wrap the message + bytes wrappedPayload = abi.encode(messageId, destAccount, payload, attributes); - return id; + // Send the message + address destGateway = address(0); // TODO + gateway.callContract(string(fromCAIP2(destChain)), destGateway, wrappedPayload); + emit MessageSent(messageId); } } From 27dcd99e4179cd56190332f593b251871d4d3aaf Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 5 Sep 2024 15:53:42 +0200 Subject: [PATCH 22/51] trying to get crosschain to compile --- contracts/crosschain/ICAIP2Equivalence.sol | 5 +- contracts/crosschain/IGatewayDestination.sol | 7 ++ ...ive.sol => IGatewayDestinationPassive.sol} | 6 +- contracts/crosschain/IGatewayIncoming.sol | 7 -- ...GatewayOutgoing.sol => IGatewaySource.sol} | 6 +- ...2Equivalence.sol => AxelarGatewayBase.sol} | 25 +++++- .../axelar/AxelarGatewayDestination.sol | 90 +++++++++++++++++++ .../axelar/AxelarGatewayIncoming.sol | 50 ----------- .../axelar/AxelarGatewayOutgoing.sol | 46 ---------- .../crosschain/axelar/AxelarGatewaySource.sol | 44 +++++++++ hardhat.config.js | 5 +- lib/@openzeppelin-contracts | 2 +- remappings.txt | 4 +- 13 files changed, 177 insertions(+), 120 deletions(-) create mode 100644 contracts/crosschain/IGatewayDestination.sol rename contracts/crosschain/{IGatewayIncomingPassive.sol => IGatewayDestinationPassive.sol} (61%) delete mode 100644 contracts/crosschain/IGatewayIncoming.sol rename contracts/crosschain/{IGatewayOutgoing.sol => IGatewaySource.sol} (76%) rename contracts/crosschain/axelar/{AxelarCAIP2Equivalence.sol => AxelarGatewayBase.sol} (86%) create mode 100644 contracts/crosschain/axelar/AxelarGatewayDestination.sol delete mode 100644 contracts/crosschain/axelar/AxelarGatewayIncoming.sol delete mode 100644 contracts/crosschain/axelar/AxelarGatewayOutgoing.sol create mode 100644 contracts/crosschain/axelar/AxelarGatewaySource.sol diff --git a/contracts/crosschain/ICAIP2Equivalence.sol b/contracts/crosschain/ICAIP2Equivalence.sol index b72dc9cb..45b47bb6 100644 --- a/contracts/crosschain/ICAIP2Equivalence.sol +++ b/contracts/crosschain/ICAIP2Equivalence.sol @@ -2,9 +2,6 @@ pragma solidity ^0.8.0; -import {IAxelarGateway} from "@axelar-network/axelar-cgp-solidity/interfaces/IAxelarGateway.sol"; -import {IAxelarGasService} from "@axelar-network/axelar-cgp-solidity/interfaces/IAxelarGasService.sol"; - /// @dev Equivalence interface between CAIP-2 chain identifiers and protocol-specific chain identifiers. /// /// See https://chainagnostic.org/CAIPs/caip-2[CAIP2]. @@ -15,5 +12,5 @@ interface ICAIP2Equivalence { function supported(string memory caip2) external view returns (bool); /// @dev Retrieves the protocol-specific chain identifier equivalent to a CAIP-2 chain identifier. - function fromCAIP2(string memory caip2) external view returns (bytes memory); + function fromCAIP2(string memory caip2) external view returns (string memory); } diff --git a/contracts/crosschain/IGatewayDestination.sol b/contracts/crosschain/IGatewayDestination.sol new file mode 100644 index 00000000..74d62c5e --- /dev/null +++ b/contracts/crosschain/IGatewayDestination.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IGatewayDestination { + event MessageExecuted(bytes32 indexed messageId); +} diff --git a/contracts/crosschain/IGatewayIncomingPassive.sol b/contracts/crosschain/IGatewayDestinationPassive.sol similarity index 61% rename from contracts/crosschain/IGatewayIncomingPassive.sol rename to contracts/crosschain/IGatewayDestinationPassive.sol index d999e1e6..7732da17 100644 --- a/contracts/crosschain/IGatewayIncomingPassive.sol +++ b/contracts/crosschain/IGatewayDestinationPassive.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8.0; -interface IGatewayIncomingPassive { - error GatewayIncomingPassiveInvalidMessage(bytes32 messageId); +interface IGatewayDestinationPassive { + error GatewayDestinationPassiveInvalidMessage(bytes32 messageDestinationId); function validateReceivedMessage( - bytes32 messageId, + bytes32 messageDestinationId, string calldata srcChain, string calldata srcAccount, bytes calldata payload, diff --git a/contracts/crosschain/IGatewayIncoming.sol b/contracts/crosschain/IGatewayIncoming.sol deleted file mode 100644 index b67eb536..00000000 --- a/contracts/crosschain/IGatewayIncoming.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -interface IGatewayIncoming { - event MessageExecuted(bytes32 indexed id); -} diff --git a/contracts/crosschain/IGatewayOutgoing.sol b/contracts/crosschain/IGatewaySource.sol similarity index 76% rename from contracts/crosschain/IGatewayOutgoing.sol rename to contracts/crosschain/IGatewaySource.sol index 69079ac9..0c2f022a 100644 --- a/contracts/crosschain/IGatewayOutgoing.sol +++ b/contracts/crosschain/IGatewaySource.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -interface IGatewayOutgoing { +interface IGatewaySource { struct Message { string source; // CAIP-10 account ID string destination; // CAIP-10 account ID @@ -10,8 +10,8 @@ interface IGatewayOutgoing { bytes attributes; } - event MessageCreated(bytes32 indexed id, Message message); - event MessageSent(bytes32 indexed id); + event MessageCreated(bytes32 indexed messageId, Message message); + event MessageSent(bytes32 indexed messageId); function sendMessage( string calldata destChain, // CAIP-2 chain ID diff --git a/contracts/crosschain/axelar/AxelarCAIP2Equivalence.sol b/contracts/crosschain/axelar/AxelarGatewayBase.sol similarity index 86% rename from contracts/crosschain/axelar/AxelarCAIP2Equivalence.sol rename to contracts/crosschain/axelar/AxelarGatewayBase.sol index 35eb4eb4..83fc6fdb 100644 --- a/contracts/crosschain/axelar/AxelarCAIP2Equivalence.sol +++ b/contracts/crosschain/axelar/AxelarGatewayBase.sol @@ -2,17 +2,36 @@ pragma solidity ^0.8.0; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IAxelarGateway} from "@axelar-network/axelar-cgp-solidity/contracts/interfaces/IAxelarGateway.sol"; import {ICAIP2Equivalence} from "../ICAIP2Equivalence.sol"; -abstract contract AxelarCAIP2Equivalence is ICAIP2Equivalence { +abstract contract AxelarGatewayBase is ICAIP2Equivalence, Ownable { + IAxelarGateway public immutable localGateway; + + mapping(string caip2 => string foreignGateway) private _foreignGateways; mapping(string caip2 => string destinationChain) private _equivalence; + constructor(IAxelarGateway _gateway) { + localGateway = _gateway; + } + function supported(string memory caip2) public view returns (bool) { return bytes(_equivalence[caip2]).length != 0; } - function fromCAIP2(string memory caip2) public view returns (bytes memory) { - return bytes(_equivalence[caip2]); + function fromCAIP2(string memory caip2) public view returns (string memory) { + return _equivalence[caip2]; + } + + function registerForeignGateway(string calldata caip2, string calldata foreignGateway) public onlyOwner { + require(bytes(_foreignGateways[caip2]).length == 0); + _foreignGateways[caip2] = foreignGateway; + // TODO emit event + } + + function getForeignGateway(string memory caip2) public view returns (string memory foreignGateway) { + return _foreignGateways[caip2]; } } diff --git a/contracts/crosschain/axelar/AxelarGatewayDestination.sol b/contracts/crosschain/axelar/AxelarGatewayDestination.sol new file mode 100644 index 00000000..5f8ade67 --- /dev/null +++ b/contracts/crosschain/axelar/AxelarGatewayDestination.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; +import {IGatewayDestination} from "../IGatewayDestination.sol"; +import {IGatewayDestinationPassive} from "../IGatewayDestinationPassive.sol"; +import {IGatewayReceiver} from "../IGatewayReceiver.sol"; +import {CAIP2} from "../../utils/CAIP-2.sol"; +import {CAIP10} from "../../utils/CAIP-10.sol"; + +abstract contract AxelarGatewayDestination is + IGatewayDestination, + // IGatewayDestinationPassive, // TODO + AxelarGatewayBase, + AxelarExecutable +{ + // function validateReceivedMessage( + // bytes32 messageDestinationId, + // string calldata srcChain, // CAIP-2 chain ID + // string calldata srcAccount, // i.e. address + // bytes calldata payload, + // bytes calldata attributes + // ) public virtual { + // address dstAccount = CAIP10.toString(msg.sender); + // if (!_isValidReceivedMessage(messageDestinationId, srcChain, srcAccount, dstAccount, msg.sender, paylod, attributes)) { + // revert GatewayDestinationPassiveInvalidMessage(messageDestinationId); + // } + // _execute(string(fromCAIP2(destChain)), srcAccount, wrappedPayload); + // } + + // function _isValidReceivedMessage( + // bytes32 messageDestinationId, + // string calldata srcChain, // CAIP-2 chain ID + // string calldata srcAccount, // i.e. address + // string calldata dstAccount, + // bytes calldata paylod, + // bytes calldata attributes + // ) internal returns (bool) { + // bytes wrappedPayload = abi.encode(messageDestinationId, dstAccount, payload, attributes); + // return gateway.validateContractCall(messageDestinationId, srcChain, srcAccount, keccak256(wrappedPayload)); + // } + + // In this function: + // - `srcChain` is in the Axelar format. It should not be expected to be a proper CAIP-2 format + // - `srcAccount` is the sender of the crosschain message. That should be the foreign gateway on the chain which + // the message originates from. It is NOT the sender of the crosschain message + // + // Proper CAIP-10 encoding of the message sender (including the CAIP-2 name of the origin chain can be found in + // the mssage) + function _execute( + string calldata srcChain, + string calldata srcAccount, + bytes calldata wrappedPayload + ) internal virtual override { + // Parse the message package + // - message identifier (from the source, not unique ?) + // - source account (caller of this gateway) + // - destination account + // - payload + // - attributes + ( + bytes32 messageId, + string memory caip10Src, + string memory caip10Dst, + bytes memory payload, + bytes memory attributes + ) = abi.decode(wrappedPayload, (bytes32, string, string, bytes, bytes)); + + (string memory originChain, string memory originAccount) = CAIP10.parse(caip10Src); + (string memory targetChain, string memory targetAccount) = CAIP10.parse(caip10Dst); + + // check message validity + // - `srcChain` matches origin chain in the message (in caip2) + // - `srcAccount` is the foreign gateway on the origin chain. + require(Strings.equal(srcChain, fromCAIP2(originChain)), "Invalid origin chain"); + require(Strings.equal(srcAccount, getForeignGateway(originChain)), "Invalid origin gateway"); + require(CAIP2.isCurrentId(targetChain), "Invalid tardet chain"); + + // TODO: not available yet + // address destination = address(uint160(Strings.toUint(targetAccount))); + targetAccount; + address destination = address(0); + + IGatewayReceiver(destination).receiveMessage(messageId, originChain, originAccount, payload, attributes); + emit MessageExecuted(messageId); + } +} diff --git a/contracts/crosschain/axelar/AxelarGatewayIncoming.sol b/contracts/crosschain/axelar/AxelarGatewayIncoming.sol deleted file mode 100644 index d0c8ec74..00000000 --- a/contracts/crosschain/axelar/AxelarGatewayIncoming.sol +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {IAxelarGateway} from "@axelar-network/axelar-cgp-solidity/interfaces/IAxelarGateway.sol"; -import {IGatewayIncomingPassive} from "../IGatewayIncomingPassive.sol"; -import {IGatewayIncoming} from "../IGatewayIncoming.sol"; -import {IGatewayReceiver} from "../IGatewayReceiver.sol"; -import {AxelarCAIP2Equivalence} from "./AxelarCAIP2Equivalence.sol"; -import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/executable/AxelarExecutable.sol"; -import {CAIP10} from "../../utils/CAIP-10.sol"; - -abstract contract AxelarGatewayIncoming is - AxelarExecutable, - AxelarCAIP2Equivalence, - IGatewayIncoming, - IGatewayIncomingPassive -{ - function validateReceivedMessage( - bytes32 messageId, - string calldata srcChain, // CAIP-2 chain ID - string calldata srcAccount, // i.e. address - bytes calldata payload, - bytes calldata attributes - ) public virtual { - address dstAccount = CAIP10.toString(msg.sender); - if (!_isValidReceivedMessage(messageId, srcChain, srcAccount, dstAccount, msg.sender, paylod, attributes)) { - revert GatewayIncomingPassiveInvalidMessage(messageId); - } - _execute(string(fromCAIP2(destChain)), srcAccount, wrappedPayload); - } - - function _isValidReceivedMessage( - bytes32 messageId, - string calldata srcChain, // CAIP-2 chain ID - string calldata srcAccount, // i.e. address - string calldata dstAccount, - bytes calldata paylod, - bytes calldata attributes - ) internal returns (bool) { - bytes wrappedPayload = abi.encode(messageId, dstAccount, payload, attributes); - return gateway.validateContractCall(messageId, srcChain, srcAccount, keccak256(wrappedPayload)); - } - - function _execute(string calldata srcChain, string calldata srcAccount, bytes calldata wrappedPayload) internal virtual { - (bytes32 messageId, string destAccount, bytes payload, bytes attributes) = abi.decode(wrappedPayload, (bytes32, string, bytes, bytes)); - IGatewayReceiver(destination).receiveMessage(messageId, srcChain, srcAccount, payload, attributes); - emit MessageExecuted(messageId); - } -} diff --git a/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol b/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol deleted file mode 100644 index 02531683..00000000 --- a/contracts/crosschain/axelar/AxelarGatewayOutgoing.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {IAxelarGateway} from "@axelar-network/axelar-cgp-solidity/interfaces/IAxelarGateway.sol"; -import {IAxelarGasService} from "@axelar-network/axelar-cgp-solidity/interfaces/IAxelarGasService.sol"; -import {IGatewayOutgoing} from "../IGatewayOutgoing.sol"; -import {AxelarCAIP2Equivalence} from "./AxelarCAIP2Equivalence.sol"; -import {CAIP2} from "../../utils/CAIP-2.sol"; -import {CAIP10} from "../../utils/CAIP-10.sol"; - -abstract contract AxelarGatewayOutgoing is IGatewayOutgoing, AxelarCAIP2Equivalence { - IAxelarGateway public immutable gateway; - - function sendMessage( - string calldata destChain, // CAIP-2 chain ID - string calldata destAccount, // i.e. address - bytes calldata payload, - bytes calldata attributes - ) external payable override returns (bytes32 messageId) { - // TODO: Handle ether (payable) - // TODO: Validate attributes - - // Validate there's an equivalent chain identifier supported by the gateway - if (!supported(destChain)) revert UnsupportedChain(destChain); - - // Create a message - Message memory message = Message( - CAIP10.currentId(Strings.toHexString(msg.sender)), - CAIP10.toString(destChain, destAccount), - payload, - attributes - ); - messageId = keccak256(abi.encode(message)); - emit MessageCreated(id, message); - - // Wrap the message - bytes wrappedPayload = abi.encode(messageId, destAccount, payload, attributes); - - // Send the message - address destGateway = address(0); // TODO - gateway.callContract(string(fromCAIP2(destChain)), destGateway, wrappedPayload); - emit MessageSent(messageId); - } -} diff --git a/contracts/crosschain/axelar/AxelarGatewaySource.sol b/contracts/crosschain/axelar/AxelarGatewaySource.sol new file mode 100644 index 00000000..0be02ae8 --- /dev/null +++ b/contracts/crosschain/axelar/AxelarGatewaySource.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.27; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; +import {IGatewaySource} from "../IGatewaySource.sol"; +import {CAIP10} from "../../utils/CAIP-10.sol"; + +abstract contract AxelarGatewaySource is IGatewaySource, AxelarGatewayBase { + function sendMessage( + string calldata dstChain, // CAIP-2 chain ID + string calldata dstAccount, // i.e. address + bytes calldata payload, + bytes calldata attributes + ) external payable override returns (bytes32) { + // TODO: Handle ether (payable) + // TODO: Validate attributes + + // Validate there's an equivalent chain identifier supported by the gateway + require(supported(dstChain), UnsupportedChain(dstChain)); + string memory caip10Src = CAIP10.currentId(Strings.toHexString(msg.sender)); + string memory caip10Dst = CAIP10.toString(dstChain, dstAccount); + string memory axelarDst = fromCAIP2(dstChain); + string memory foreignGateway = getForeignGateway(dstChain); + + // Create a message package + // - message identifier (from the source, not unique ?) + // - source account (caller of this gateway) + // - destination account + // - payload + // - attributes + bytes32 messageId = bytes32(0); // TODO: counter ? + bytes memory package = abi.encode(messageId, caip10Src, caip10Dst, payload, attributes); + + // emit event + emit MessageCreated(messageId, Message(caip10Src, caip10Dst, payload, attributes)); + + // Send the message + localGateway.callContract(axelarDst, foreignGateway, package); + + return messageId; + } +} diff --git a/hardhat.config.js b/hardhat.config.js index 803dbaf3..1d8afdea 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -3,7 +3,7 @@ const { argv } = require('yargs/yargs')() .options({ compiler: { type: 'string', - default: '0.8.26', + default: '0.8.27', }, hardfork: { type: 'string', @@ -33,4 +33,7 @@ module.exports = { hardfork: argv.hardfork, }, }, + exposed: { + exclude: ['@axelar-network/**/*'], + }, }; diff --git a/lib/@openzeppelin-contracts b/lib/@openzeppelin-contracts index 52c36d41..cb7faaf4 160000 --- a/lib/@openzeppelin-contracts +++ b/lib/@openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 52c36d412e8681053975396223d0ea39687fe33b +Subproject commit cb7faaf4db9d1ea443b507311487625220e5e215 diff --git a/remappings.txt b/remappings.txt index 138ddb98..1f3c23e4 100644 --- a/remappings.txt +++ b/remappings.txt @@ -6,5 +6,5 @@ @openzeppelin/community-contracts/=contracts/ -@axelar-network/axelar-cgp-solidity/=node_modules/@axelar-network/axelar-cgp-solidity/contracts/ -@axelar-network/axelar-gmp-sdk-solidity/=node_modules/@axelar-network/axelar-gmp-sdk-solidity/contracts/ +@axelar-network/axelar-cgp-solidity/=node_modules/@axelar-network/axelar-cgp-solidity/ +@axelar-network/axelar-gmp-sdk-solidity/=node_modules/@axelar-network/axelar-gmp-sdk-solidity/ From 01d98fde04358c09881e4b98c70748f41adb9410 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 5 Sep 2024 16:42:59 +0200 Subject: [PATCH 23/51] fix compilation --- contracts/crosschain/axelar/AxelarGatewayBase.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/crosschain/axelar/AxelarGatewayBase.sol b/contracts/crosschain/axelar/AxelarGatewayBase.sol index 83fc6fdb..41b2cb7e 100644 --- a/contracts/crosschain/axelar/AxelarGatewayBase.sol +++ b/contracts/crosschain/axelar/AxelarGatewayBase.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {IAxelarGateway} from "@axelar-network/axelar-cgp-solidity/contracts/interfaces/IAxelarGateway.sol"; +import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol"; import {ICAIP2Equivalence} from "../ICAIP2Equivalence.sol"; abstract contract AxelarGatewayBase is ICAIP2Equivalence, Ownable { From 85ee12a89f230285c440b5f0a68e9dbe801f004f Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 5 Sep 2024 17:25:02 +0200 Subject: [PATCH 24/51] minor update --- .../crosschain/axelar/AxelarGatewayDestination.sol | 10 ++++++---- contracts/crosschain/axelar/AxelarGatewaySource.sol | 3 +++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/contracts/crosschain/axelar/AxelarGatewayDestination.sol b/contracts/crosschain/axelar/AxelarGatewayDestination.sol index 5f8ade67..98c59242 100644 --- a/contracts/crosschain/axelar/AxelarGatewayDestination.sol +++ b/contracts/crosschain/axelar/AxelarGatewayDestination.sol @@ -39,8 +39,8 @@ abstract contract AxelarGatewayDestination is // bytes calldata paylod, // bytes calldata attributes // ) internal returns (bool) { - // bytes wrappedPayload = abi.encode(messageDestinationId, dstAccount, payload, attributes); - // return gateway.validateContractCall(messageDestinationId, srcChain, srcAccount, keccak256(wrappedPayload)); + // bytes package = abi.encode(messageDestinationId, dstAccount, payload, attributes); + // return gateway.validateContractCall(messageDestinationId, srcChain, srcAccount, keccak256(package)); // } // In this function: @@ -53,7 +53,7 @@ abstract contract AxelarGatewayDestination is function _execute( string calldata srcChain, string calldata srcAccount, - bytes calldata wrappedPayload + bytes calldata package ) internal virtual override { // Parse the message package // - message identifier (from the source, not unique ?) @@ -67,7 +67,7 @@ abstract contract AxelarGatewayDestination is string memory caip10Dst, bytes memory payload, bytes memory attributes - ) = abi.decode(wrappedPayload, (bytes32, string, string, bytes, bytes)); + ) = abi.decode(package, (bytes32, string, string, bytes, bytes)); (string memory originChain, string memory originAccount) = CAIP10.parse(caip10Src); (string memory targetChain, string memory targetAccount) = CAIP10.parse(caip10Dst); @@ -77,6 +77,7 @@ abstract contract AxelarGatewayDestination is // - `srcAccount` is the foreign gateway on the origin chain. require(Strings.equal(srcChain, fromCAIP2(originChain)), "Invalid origin chain"); require(Strings.equal(srcAccount, getForeignGateway(originChain)), "Invalid origin gateway"); + // This check is not required for security. That is enforced by axelar (+ source gateway) require(CAIP2.isCurrentId(targetChain), "Invalid tardet chain"); // TODO: not available yet @@ -85,6 +86,7 @@ abstract contract AxelarGatewayDestination is address destination = address(0); IGatewayReceiver(destination).receiveMessage(messageId, originChain, originAccount, payload, attributes); + emit MessageExecuted(messageId); } } diff --git a/contracts/crosschain/axelar/AxelarGatewaySource.sol b/contracts/crosschain/axelar/AxelarGatewaySource.sol index 0be02ae8..2f31efcd 100644 --- a/contracts/crosschain/axelar/AxelarGatewaySource.sol +++ b/contracts/crosschain/axelar/AxelarGatewaySource.sol @@ -39,6 +39,9 @@ abstract contract AxelarGatewaySource is IGatewaySource, AxelarGatewayBase { // Send the message localGateway.callContract(axelarDst, foreignGateway, package); + // TODO + // emit MessageSent(bytes32(0)); + return messageId; } } From cdc3c94172f54cf2259dbef594c593b6b93f13c0 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 5 Sep 2024 17:39:46 +0200 Subject: [PATCH 25/51] make attributes a bytes[] --- contracts/crosschain/IGatewayDestinationPassive.sol | 2 +- contracts/crosschain/IGatewayReceiver.sol | 2 +- contracts/crosschain/IGatewaySource.sol | 4 ++-- contracts/crosschain/axelar/AxelarGatewayDestination.sol | 4 ++-- contracts/crosschain/axelar/AxelarGatewaySource.sol | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/crosschain/IGatewayDestinationPassive.sol b/contracts/crosschain/IGatewayDestinationPassive.sol index 7732da17..206ae91d 100644 --- a/contracts/crosschain/IGatewayDestinationPassive.sol +++ b/contracts/crosschain/IGatewayDestinationPassive.sol @@ -10,6 +10,6 @@ interface IGatewayDestinationPassive { string calldata srcChain, string calldata srcAccount, bytes calldata payload, - bytes calldata attributes + bytes[] calldata attributes ) external; } diff --git a/contracts/crosschain/IGatewayReceiver.sol b/contracts/crosschain/IGatewayReceiver.sol index 6035ebfe..b4f603b9 100644 --- a/contracts/crosschain/IGatewayReceiver.sol +++ b/contracts/crosschain/IGatewayReceiver.sol @@ -8,6 +8,6 @@ interface IGatewayReceiver { string calldata srcChain, string calldata srcAccount, bytes calldata payload, - bytes calldata attributes + bytes[] calldata attributes ) external payable; } diff --git a/contracts/crosschain/IGatewaySource.sol b/contracts/crosschain/IGatewaySource.sol index 0c2f022a..06b374dd 100644 --- a/contracts/crosschain/IGatewaySource.sol +++ b/contracts/crosschain/IGatewaySource.sol @@ -7,7 +7,7 @@ interface IGatewaySource { string source; // CAIP-10 account ID string destination; // CAIP-10 account ID bytes payload; - bytes attributes; + bytes[] attributes; } event MessageCreated(bytes32 indexed messageId, Message message); @@ -17,6 +17,6 @@ interface IGatewaySource { string calldata destChain, // CAIP-2 chain ID string calldata destAccount, // i.e. address bytes calldata payload, - bytes calldata attributes + bytes[] calldata attributes ) external payable returns (bytes32 messageId); } diff --git a/contracts/crosschain/axelar/AxelarGatewayDestination.sol b/contracts/crosschain/axelar/AxelarGatewayDestination.sol index 98c59242..2f1223bf 100644 --- a/contracts/crosschain/axelar/AxelarGatewayDestination.sol +++ b/contracts/crosschain/axelar/AxelarGatewayDestination.sol @@ -66,8 +66,8 @@ abstract contract AxelarGatewayDestination is string memory caip10Src, string memory caip10Dst, bytes memory payload, - bytes memory attributes - ) = abi.decode(package, (bytes32, string, string, bytes, bytes)); + bytes[] memory attributes + ) = abi.decode(package, (bytes32, string, string, bytes, bytes[])); (string memory originChain, string memory originAccount) = CAIP10.parse(caip10Src); (string memory targetChain, string memory targetAccount) = CAIP10.parse(caip10Dst); diff --git a/contracts/crosschain/axelar/AxelarGatewaySource.sol b/contracts/crosschain/axelar/AxelarGatewaySource.sol index 2f31efcd..b9c6af41 100644 --- a/contracts/crosschain/axelar/AxelarGatewaySource.sol +++ b/contracts/crosschain/axelar/AxelarGatewaySource.sol @@ -12,7 +12,7 @@ abstract contract AxelarGatewaySource is IGatewaySource, AxelarGatewayBase { string calldata dstChain, // CAIP-2 chain ID string calldata dstAccount, // i.e. address bytes calldata payload, - bytes calldata attributes + bytes[] calldata attributes ) external payable override returns (bytes32) { // TODO: Handle ether (payable) // TODO: Validate attributes From d7ce22993d91e44a57147f03f1582b3d61925233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 10 Sep 2024 01:07:43 -0600 Subject: [PATCH 26/51] Address comments and add some tests --- contracts/crosschain/ICAIP2Equivalence.sol | 3 - .../crosschain/axelar/AxelarGatewayBase.sol | 27 +++++--- .../axelar/AxelarGatewayDestination.sol | 34 ++-------- .../crosschain/axelar/AxelarGatewaySource.sol | 8 +-- contracts/mocks/AxelarGatewayMock.sol | 15 +++++ lib/@openzeppelin-contracts | 2 +- package-lock.json | 20 ------ package.json | 1 - remappings.txt | 1 - .../axelar/AxelarGatewayBase.test.js | 67 +++++++++++++++++++ .../axelar/AxelarGatewayDestination.test.js | 18 +++++ .../axelar/AxelarGatewaySource.test.js | 18 +++++ 12 files changed, 143 insertions(+), 71 deletions(-) create mode 100644 contracts/mocks/AxelarGatewayMock.sol create mode 100644 test/crosschain/axelar/AxelarGatewayBase.test.js create mode 100644 test/crosschain/axelar/AxelarGatewayDestination.test.js create mode 100644 test/crosschain/axelar/AxelarGatewaySource.test.js diff --git a/contracts/crosschain/ICAIP2Equivalence.sol b/contracts/crosschain/ICAIP2Equivalence.sol index 45b47bb6..02f49562 100644 --- a/contracts/crosschain/ICAIP2Equivalence.sol +++ b/contracts/crosschain/ICAIP2Equivalence.sol @@ -8,9 +8,6 @@ pragma solidity ^0.8.0; interface ICAIP2Equivalence { error UnsupportedChain(string caip2); - /// @dev Checks if a CAIP-2 chain identifier is registered as equivalent to a protocol-specific chain identifier. - function supported(string memory caip2) external view returns (bool); - /// @dev Retrieves the protocol-specific chain identifier equivalent to a CAIP-2 chain identifier. function fromCAIP2(string memory caip2) external view returns (string memory); } diff --git a/contracts/crosschain/axelar/AxelarGatewayBase.sol b/contracts/crosschain/axelar/AxelarGatewayBase.sol index 41b2cb7e..703ca4f4 100644 --- a/contracts/crosschain/axelar/AxelarGatewayBase.sol +++ b/contracts/crosschain/axelar/AxelarGatewayBase.sol @@ -7,31 +7,36 @@ import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/ import {ICAIP2Equivalence} from "../ICAIP2Equivalence.sol"; abstract contract AxelarGatewayBase is ICAIP2Equivalence, Ownable { + event RegisteredRemoteGateway(string caip2, string gatewayAddress); + event RegisteredCAIP2Equivalence(string caip2, string destinationChain); + IAxelarGateway public immutable localGateway; - mapping(string caip2 => string foreignGateway) private _foreignGateways; + mapping(string caip2 => string remoteGateway) private _remoteGateways; mapping(string caip2 => string destinationChain) private _equivalence; constructor(IAxelarGateway _gateway) { localGateway = _gateway; } - function supported(string memory caip2) public view returns (bool) { - return bytes(_equivalence[caip2]).length != 0; - } - function fromCAIP2(string memory caip2) public view returns (string memory) { return _equivalence[caip2]; } - function registerForeignGateway(string calldata caip2, string calldata foreignGateway) public onlyOwner { - require(bytes(_foreignGateways[caip2]).length == 0); - _foreignGateways[caip2] = foreignGateway; - // TODO emit event + function getRemoteGateway(string memory caip2) public view returns (string memory remoteGateway) { + return _remoteGateways[caip2]; + } + + function registerCAIP2Equivalence(string calldata caip2, string calldata axelarSupported) public onlyOwner { + require(bytes(_equivalence[caip2]).length == 0); + _equivalence[caip2] = axelarSupported; + emit RegisteredCAIP2Equivalence(caip2, axelarSupported); } - function getForeignGateway(string memory caip2) public view returns (string memory foreignGateway) { - return _foreignGateways[caip2]; + function registerRemoteGateway(string calldata caip2, string calldata remoteGateway) public onlyOwner { + require(bytes(_remoteGateways[caip2]).length == 0); + _remoteGateways[caip2] = remoteGateway; + emit RegisteredRemoteGateway(caip2, remoteGateway); } } diff --git a/contracts/crosschain/axelar/AxelarGatewayDestination.sol b/contracts/crosschain/axelar/AxelarGatewayDestination.sol index 2f1223bf..0a48f6d8 100644 --- a/contracts/crosschain/axelar/AxelarGatewayDestination.sol +++ b/contracts/crosschain/axelar/AxelarGatewayDestination.sol @@ -17,39 +17,13 @@ abstract contract AxelarGatewayDestination is AxelarGatewayBase, AxelarExecutable { - // function validateReceivedMessage( - // bytes32 messageDestinationId, - // string calldata srcChain, // CAIP-2 chain ID - // string calldata srcAccount, // i.e. address - // bytes calldata payload, - // bytes calldata attributes - // ) public virtual { - // address dstAccount = CAIP10.toString(msg.sender); - // if (!_isValidReceivedMessage(messageDestinationId, srcChain, srcAccount, dstAccount, msg.sender, paylod, attributes)) { - // revert GatewayDestinationPassiveInvalidMessage(messageDestinationId); - // } - // _execute(string(fromCAIP2(destChain)), srcAccount, wrappedPayload); - // } - - // function _isValidReceivedMessage( - // bytes32 messageDestinationId, - // string calldata srcChain, // CAIP-2 chain ID - // string calldata srcAccount, // i.e. address - // string calldata dstAccount, - // bytes calldata paylod, - // bytes calldata attributes - // ) internal returns (bool) { - // bytes package = abi.encode(messageDestinationId, dstAccount, payload, attributes); - // return gateway.validateContractCall(messageDestinationId, srcChain, srcAccount, keccak256(package)); - // } - // In this function: // - `srcChain` is in the Axelar format. It should not be expected to be a proper CAIP-2 format - // - `srcAccount` is the sender of the crosschain message. That should be the foreign gateway on the chain which + // - `srcAccount` is the sender of the crosschain message. That should be the remote gateway on the chain which // the message originates from. It is NOT the sender of the crosschain message // // Proper CAIP-10 encoding of the message sender (including the CAIP-2 name of the origin chain can be found in - // the mssage) + // the message) function _execute( string calldata srcChain, string calldata srcAccount, @@ -74,9 +48,9 @@ abstract contract AxelarGatewayDestination is // check message validity // - `srcChain` matches origin chain in the message (in caip2) - // - `srcAccount` is the foreign gateway on the origin chain. + // - `srcAccount` is the remote gateway on the origin chain. require(Strings.equal(srcChain, fromCAIP2(originChain)), "Invalid origin chain"); - require(Strings.equal(srcAccount, getForeignGateway(originChain)), "Invalid origin gateway"); + require(Strings.equal(srcAccount, getRemoteGateway(originChain)), "Invalid origin gateway"); // This check is not required for security. That is enforced by axelar (+ source gateway) require(CAIP2.isCurrentId(targetChain), "Invalid tardet chain"); diff --git a/contracts/crosschain/axelar/AxelarGatewaySource.sol b/contracts/crosschain/axelar/AxelarGatewaySource.sol index b9c6af41..f2fead43 100644 --- a/contracts/crosschain/axelar/AxelarGatewaySource.sol +++ b/contracts/crosschain/axelar/AxelarGatewaySource.sol @@ -18,11 +18,11 @@ abstract contract AxelarGatewaySource is IGatewaySource, AxelarGatewayBase { // TODO: Validate attributes // Validate there's an equivalent chain identifier supported by the gateway - require(supported(dstChain), UnsupportedChain(dstChain)); + string memory axelarDstChainId = fromCAIP2(dstChain); + require(bytes(axelarDstChainId).length > 0, UnsupportedChain(dstChain)); string memory caip10Src = CAIP10.currentId(Strings.toHexString(msg.sender)); string memory caip10Dst = CAIP10.toString(dstChain, dstAccount); - string memory axelarDst = fromCAIP2(dstChain); - string memory foreignGateway = getForeignGateway(dstChain); + string memory remoteGateway = getRemoteGateway(dstChain); // Create a message package // - message identifier (from the source, not unique ?) @@ -37,7 +37,7 @@ abstract contract AxelarGatewaySource is IGatewaySource, AxelarGatewayBase { emit MessageCreated(messageId, Message(caip10Src, caip10Dst, payload, attributes)); // Send the message - localGateway.callContract(axelarDst, foreignGateway, package); + localGateway.callContract(axelarDstChainId, remoteGateway, package); // TODO // emit MessageSent(bytes32(0)); diff --git a/contracts/mocks/AxelarGatewayMock.sol b/contracts/mocks/AxelarGatewayMock.sol new file mode 100644 index 00000000..27d737b3 --- /dev/null +++ b/contracts/mocks/AxelarGatewayMock.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.27; + +contract AxelarGatewayMock { + event CallContract(string destinationChain, string contractAddress, bytes payload); + + function callContract( + string calldata destinationChain, + string calldata contractAddress, + bytes calldata payload + ) external { + emit CallContract(destinationChain, contractAddress, payload); + } +} diff --git a/lib/@openzeppelin-contracts b/lib/@openzeppelin-contracts index cb7faaf4..52c36d41 160000 --- a/lib/@openzeppelin-contracts +++ b/lib/@openzeppelin-contracts @@ -1 +1 @@ -Subproject commit cb7faaf4db9d1ea443b507311487625220e5e215 +Subproject commit 52c36d412e8681053975396223d0ea39687fe33b diff --git a/package-lock.json b/package-lock.json index 0e196841..64b808f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.0.1", "license": "MIT", "dependencies": { - "@axelar-network/axelar-cgp-solidity": "^6.3.1", "@axelar-network/axelar-gmp-sdk-solidity": "^5.10.0", "@openzeppelin/contracts": "^5.0.2", "@openzeppelin/contracts-upgradeable": "^5.0.2" @@ -33,25 +32,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@axelar-network/axelar-cgp-solidity": { - "version": "6.3.1", - "license": "MIT", - "dependencies": { - "@axelar-network/axelar-gmp-sdk-solidity": "5.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@axelar-network/axelar-cgp-solidity/node_modules/@axelar-network/axelar-gmp-sdk-solidity": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@axelar-network/axelar-gmp-sdk-solidity/-/axelar-gmp-sdk-solidity-5.8.0.tgz", - "integrity": "sha512-ThiCWK7lhwmsipgjKkw8c0z0ubB9toRMV9X0tRVOXHHSknKp5DCFfatbCwjpSC5GZRa+61ciTSqJNtCc7j9YoQ==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/@axelar-network/axelar-gmp-sdk-solidity": { "version": "5.10.0", "resolved": "https://registry.npmjs.org/@axelar-network/axelar-gmp-sdk-solidity/-/axelar-gmp-sdk-solidity-5.10.0.tgz", diff --git a/package.json b/package.json index 02b8f1aa..61fb6055 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "zeppelin" ], "dependencies": { - "@axelar-network/axelar-cgp-solidity": "^6.3.1", "@axelar-network/axelar-gmp-sdk-solidity": "^5.10.0", "@openzeppelin/contracts": "^5.0.2", "@openzeppelin/contracts-upgradeable": "^5.0.2" diff --git a/remappings.txt b/remappings.txt index 1f3c23e4..090202de 100644 --- a/remappings.txt +++ b/remappings.txt @@ -6,5 +6,4 @@ @openzeppelin/community-contracts/=contracts/ -@axelar-network/axelar-cgp-solidity/=node_modules/@axelar-network/axelar-cgp-solidity/ @axelar-network/axelar-gmp-sdk-solidity/=node_modules/@axelar-network/axelar-gmp-sdk-solidity/ diff --git a/test/crosschain/axelar/AxelarGatewayBase.test.js b/test/crosschain/axelar/AxelarGatewayBase.test.js new file mode 100644 index 00000000..bb3af846 --- /dev/null +++ b/test/crosschain/axelar/AxelarGatewayBase.test.js @@ -0,0 +1,67 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { toBeHex, hexlify } = require('ethers'); + +async function fixture() { + const [owner, other] = await ethers.getSigners(); + + const localGateway = await ethers.deployContract('AxelarGatewayMock'); + const mock = await ethers.deployContract('$AxelarGatewayBase', [owner, localGateway]); + return { owner, other, mock }; +} + +describe('AxelarGatewayBase', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('fromCAIP2', function () { + it('returns empty string if the chain is not supported', async function () { + expect(await this.mock['fromCAIP2(string)']('eip155:11155111')).to.equal(''); + }); + + it('returns the chain name if the chain is supported', async function () { + await this.mock.connect(this.owner).registerCAIP2Equivalence('eip155:1', 'Ethereum'); + expect(await this.mock['fromCAIP2(string)']('eip155:1')).to.equal('Ethereum'); + }); + }); + + describe('getRemoteGateway', function () { + it('returns empty string if there is no remote gateway', async function () { + expect(await this.mock.getRemoteGateway('unknown:unknown')).to.equal(''); + }); + + it('returns the remote gateway if it exists', async function () { + await this.mock.connect(this.owner).registerRemoteGateway('eip155:1', this.other.address); + expect(await this.mock.getRemoteGateway('eip155:1')).to.equal(this.other.address); + }); + }); + + describe('registerCAIP2Equivalence', function () { + it('emits an event', async function () { + await expect(this.mock.connect(this.owner).registerCAIP2Equivalence('eip155:1', 'Ethereum')) + .to.emit(this.mock, 'RegisteredCAIP2Equivalence') + .withArgs('eip155:1', 'Ethereum'); + }); + + it('reverts if the chain is already registered', async function () { + await this.mock.connect(this.owner).registerCAIP2Equivalence('eip155:1', 'Ethereum'); + await expect(this.mock.connect(this.owner).registerCAIP2Equivalence('eip155:1', 'Ethereum')).to.be.reverted; + }); + }); + + describe('registerRemoteGateway', function () { + it('emits an event', async function () { + await expect(this.mock.connect(this.owner).registerRemoteGateway('eip155:1', this.other.address)) + .to.emit(this.mock, 'RegisteredRemoteGateway') + .withArgs('eip155:1', this.other.address); + }); + + it('reverts if the chain is already registered', async function () { + await this.mock.connect(this.owner).registerRemoteGateway('eip155:1', this.other.address); // register once + await expect(this.mock.connect(this.owner).registerRemoteGateway('eip155:1', this.other.address)).to.be + .reverted; + }); + }); +}); diff --git a/test/crosschain/axelar/AxelarGatewayDestination.test.js b/test/crosschain/axelar/AxelarGatewayDestination.test.js new file mode 100644 index 00000000..fbaa7d9d --- /dev/null +++ b/test/crosschain/axelar/AxelarGatewayDestination.test.js @@ -0,0 +1,18 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + +async function fixture() { + const mock = await ethers.deployContract('$AxelarGatewayDestination'); + return { mock }; +} + +describe('AxelarGatewayDestination', function () { + before(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('_execute', function () { + // TODO: Add tests + }); +}); diff --git a/test/crosschain/axelar/AxelarGatewaySource.test.js b/test/crosschain/axelar/AxelarGatewaySource.test.js new file mode 100644 index 00000000..016fe60a --- /dev/null +++ b/test/crosschain/axelar/AxelarGatewaySource.test.js @@ -0,0 +1,18 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + +async function fixture() { + const mock = await ethers.deployContract('$AxelarGatewaySource'); + return { mock }; +} + +describe('AxelarGatewaySource', function () { + before(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('_execute', function () { + // TODO: Add tests + }); +}); From 6dc1c445f20aa09bde81e80aafd492f06484b584 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 10 Sep 2024 16:57:14 +0200 Subject: [PATCH 27/51] refactor and test caip utils --- .../axelar/AxelarGatewayDestination.sol | 8 ++- .../crosschain/axelar/AxelarGatewaySource.sol | 4 +- contracts/utils/Bytes.sol | 57 +++++++++++++-- contracts/utils/CAIP-10.sol | 47 +++--------- contracts/utils/CAIP-2.sol | 71 +++---------------- test/crosschain/utils/CAIP.test.js | 67 +++++++++++++++++ 6 files changed, 146 insertions(+), 108 deletions(-) create mode 100644 test/crosschain/utils/CAIP.test.js diff --git a/contracts/crosschain/axelar/AxelarGatewayDestination.sol b/contracts/crosschain/axelar/AxelarGatewayDestination.sol index 0a48f6d8..672a9061 100644 --- a/contracts/crosschain/axelar/AxelarGatewayDestination.sol +++ b/contracts/crosschain/axelar/AxelarGatewayDestination.sol @@ -17,6 +17,8 @@ abstract contract AxelarGatewayDestination is AxelarGatewayBase, AxelarExecutable { + using Strings for string; + // In this function: // - `srcChain` is in the Axelar format. It should not be expected to be a proper CAIP-2 format // - `srcAccount` is the sender of the crosschain message. That should be the remote gateway on the chain which @@ -49,10 +51,10 @@ abstract contract AxelarGatewayDestination is // check message validity // - `srcChain` matches origin chain in the message (in caip2) // - `srcAccount` is the remote gateway on the origin chain. - require(Strings.equal(srcChain, fromCAIP2(originChain)), "Invalid origin chain"); - require(Strings.equal(srcAccount, getRemoteGateway(originChain)), "Invalid origin gateway"); + require(fromCAIP2(originChain).equal(srcChain), "Invalid origin chain"); + require(getRemoteGateway(originChain).equal(srcAccount), "Invalid origin gateway"); // This check is not required for security. That is enforced by axelar (+ source gateway) - require(CAIP2.isCurrentId(targetChain), "Invalid tardet chain"); + require(CAIP2.format().equal(targetChain), "Invalid tardet chain"); // TODO: not available yet // address destination = address(uint160(Strings.toUint(targetAccount))); diff --git a/contracts/crosschain/axelar/AxelarGatewaySource.sol b/contracts/crosschain/axelar/AxelarGatewaySource.sol index f2fead43..f6d4ae18 100644 --- a/contracts/crosschain/axelar/AxelarGatewaySource.sol +++ b/contracts/crosschain/axelar/AxelarGatewaySource.sol @@ -20,8 +20,8 @@ abstract contract AxelarGatewaySource is IGatewaySource, AxelarGatewayBase { // Validate there's an equivalent chain identifier supported by the gateway string memory axelarDstChainId = fromCAIP2(dstChain); require(bytes(axelarDstChainId).length > 0, UnsupportedChain(dstChain)); - string memory caip10Src = CAIP10.currentId(Strings.toHexString(msg.sender)); - string memory caip10Dst = CAIP10.toString(dstChain, dstAccount); + string memory caip10Src = CAIP10.format(msg.sender); + string memory caip10Dst = CAIP10.format(dstChain, dstAccount); string memory remoteGateway = getRemoteGateway(dstChain); // Create a message package diff --git a/contracts/utils/Bytes.sol b/contracts/utils/Bytes.sol index ecd97f34..3032b3dc 100644 --- a/contracts/utils/Bytes.sol +++ b/contracts/utils/Bytes.sol @@ -2,13 +2,58 @@ pragma solidity ^0.8.0; library Bytes { - function find(bytes memory input, bytes1 chr, uint256 cursor) internal pure returns (uint256) { - uint256 length = input.length; - for (uint256 i = cursor; i < length; ++i) { - if (input[i] == chr) { - return i; + /// @dev Forward search for `s` in `buffer` + /// * If `s` is present in the buffer, returns the index of the first instance + /// * If `s` is not present in the buffer, returns the length of the buffer + function find(bytes memory buffer, bytes1 s) internal pure returns (uint256) { + return find(buffer, s, 0); + } + + /// @dev Forward search for `s` in `buffer` starting at position `pos` + /// * If `s` is present in the buffer (at or after `pos`), returns the index of the next instance + /// * If `s` is not present in the buffer (at or after `pos`), returns the length of the buffer + function find(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { + unchecked { + uint256 length = buffer.length; + for (uint256 i = pos; i < length; ++i) { + if (buffer[i] == s) { + return i; + } } + return length; + } + } + + /// @dev Backward search for `s` in `buffer` + /// * If `s` is present in the buffer, returns the index of the last instance + /// * If `s` is not present in the buffer, returns the length of the buffer + function findLastOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) { + return findLastOf(buffer, s, buffer.length); + } + + /// @dev Backward search for `s` in `buffer` starting at position `pos` + /// * If `s` is present in the buffer (before `pos`), returns the index of the previous instance + /// * If `s` is not present in the buffer (before `pos`), returns the length of the buffer + function findLastOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { + unchecked { + for (uint256 i = pos; i > 0; --i) { + if (buffer[i - 1] == s) { + return i - 1; + } + } + return buffer.length; + } + } + + function slice(bytes memory buffer, uint256 start) internal pure returns (bytes memory) { + return slice(buffer, start, buffer.length); + } + + function slice(bytes memory buffer, uint256 start, uint256 end) internal pure returns (bytes memory) { + bytes memory result = new bytes(end - start); + for (uint256 i = start; i < end; ++i) { + result[i - start] = buffer[i]; } - return length; + return result; } } diff --git a/contracts/utils/CAIP-10.sol b/contracts/utils/CAIP-10.sol index 763195e3..1df51fa0 100644 --- a/contracts/utils/CAIP-10.sol +++ b/contracts/utils/CAIP-10.sol @@ -3,55 +3,30 @@ pragma solidity ^0.8.0; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import {CAIP2} from "./CAIP-2.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {Bytes} from "./Bytes.sol"; +import {CAIP2} from "./CAIP-2.sol"; // account_id: chain_id + ":" + account_address // chain_id: [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See [CAIP-2][]) // account_address: [-.%a-zA-Z0-9]{1,128} library CAIP10 { using SafeCast for uint256; + using Strings for address; using Bytes for bytes; - bytes1 private constant COLON = ":"; - - function toString(string memory caip2, string memory accountId) internal pure returns (string memory) { - return string(abi.encodePacked(caip2, COLON, accountId)); + function format(address account) internal view returns (string memory) { + return format(CAIP2.format(), account.toHexString()); } - function parse(string memory caip10) internal pure returns (string memory caip2, string memory accountId) { - bytes memory accountBuffer = bytes(caip10); - uint8 firstSeparatorIndex = accountBuffer.find(COLON, 0).toUint8(); - uint256 lastSeparatorIndex = accountBuffer.find(COLON, firstSeparatorIndex).toUint8(); - return (_extractCAIP2(accountBuffer, lastSeparatorIndex), _extractAccountId(accountBuffer, lastSeparatorIndex)); + function format(string memory caip2, string memory account) internal pure returns (string memory) { + return string.concat(caip2, ":", account); } - function currentId(string memory accountId) internal view returns (string memory) { - (bytes8 namespace, bytes32 ref) = CAIP2.currentId(); - return toString(CAIP2.toString(namespace, ref), accountId); - } - - function _extractCAIP2( - bytes memory accountBuffer, - uint256 lastSeparatorIndex - ) private pure returns (string memory chainId) { - bytes memory _chainId = new bytes(lastSeparatorIndex); - for (uint256 i = 0; i < lastSeparatorIndex; i++) { - _chainId[i] = accountBuffer[i]; - } - return string(_chainId); - } + function parse(string memory caip10) internal pure returns (string memory caip2, string memory account) { + bytes memory buffer = bytes(caip10); - function _extractAccountId( - bytes memory accountBuffer, - uint256 lastSeparatorIndex - ) private pure returns (string memory) { - uint256 length = accountBuffer.length; - uint256 offset = lastSeparatorIndex - 1; - bytes memory _accountId = new bytes(length - offset); // Will overflow if no separator is found - for (uint256 i = lastSeparatorIndex + 1; i < length; i++) { - _accountId[i - offset] = accountBuffer[i]; - } - return string(_accountId); + uint256 pos = buffer.findLastOf(":"); + return (string(buffer.slice(0, pos)), string(buffer.slice(pos + 1))); } } diff --git a/contracts/utils/CAIP-2.sol b/contracts/utils/CAIP-2.sol index 558c6473..4490e701 100644 --- a/contracts/utils/CAIP-2.sol +++ b/contracts/utils/CAIP-2.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {Bytes} from "./Bytes.sol"; // chain_id: namespace + ":" + reference @@ -10,73 +11,21 @@ import {Bytes} from "./Bytes.sol"; // reference: [-_a-zA-Z0-9]{1,32} library CAIP2 { using SafeCast for uint256; + using Strings for uint256; using Bytes for bytes; - bytes16 private constant HEX_DIGITS = "0123456789abcdef"; - bytes1 private constant SEMICOLON = ":"; - bytes32 private constant EVM_REFERENCE = bytes32("eip155"); // EIP-155 for EVM chains - - /// @dev Converts a namespace and reference to a CAIP2 chain identifier. - function toString(bytes8 namespace, bytes32 ref) internal pure returns (string memory) { - return string(abi.encodePacked(namespace, SEMICOLON, ref)); - } - - /// @dev Parses a CAIP2 identifier from a string by splitting it at the first semicolon. - /// The function parses both sides as `bytes8` and `bytes32` respectively without any validation. - function parse(string memory caip2) internal pure returns (bytes8 namespace, bytes32 ref) { - bytes memory chainBuffer = bytes(caip2); - uint8 semicolonIndex = chainBuffer.find(SEMICOLON, 0).toUint8(); - return (_extractNamespace(chainBuffer, semicolonIndex), _unsafeExtractReference(chainBuffer, semicolonIndex)); - } - - /// @dev Checks if the given CAIP2 identifier is the current chain. - function isCurrentId(string memory caip2) internal view returns (bool) { - (bytes8 namespace, bytes32 ref) = parse(caip2); - (bytes8 _namespace, bytes32 _ref) = currentId(); - return namespace == _namespace && ref == _ref; + function format() internal view returns (string memory) { + return format("eip155", block.chainid.toString()); } - /// @dev Returns the CAIP2 identifier of the current chain. - function currentId() internal view returns (bytes8 namespace, bytes32 ref) { - return (currentNamespace(), currentReference()); + function format(string memory namespace, string memory ref) internal pure returns (string memory) { + return string.concat(namespace, ":", ref); } - /// @dev Returns the CAIP2 identifier of the current chain. - /// Assumes block.chainId < type(uint64).max - function currentNamespace() internal view returns (bytes8 _chainId) { - unchecked { - uint256 id = block.chainid; - while (true) { - _chainId = bytes8(uint64(_chainId) - 1); - assembly ("memory-safe") { - mstore8(_chainId, byte(mod(id, 10), HEX_DIGITS)) - } - id /= 10; - if (id == 0) break; - } - } - } - - /// @dev Returns the reference of the current chain. - function currentReference() internal pure returns (bytes32) { - return EVM_REFERENCE; - } - - /// @dev Extracts the first `semicolonIndex` bytes from the chain buffer as a bytes8 namespace. - function _extractNamespace(bytes memory chainBuffer, uint8 semicolonIndex) private pure returns (bytes8 namespace) { - assembly ("memory-safe") { - let shift := sub(256, mul(semicolonIndex, 8)) - namespace := shl(shift, shr(shift, mload(add(chainBuffer, 0x20)))) - } - } + function parse(string memory caip2) internal pure returns (string memory namespace, string memory ref) { + bytes memory buffer = bytes(caip2); - /// @dev Extracts the reference from the chain buffer after the semicolon located at `offset`. - /// - /// IMPORTANT: The caller must make sure that the semicolon index is within the chain buffer length - /// and that there are 32 bytes available after the semicolon. Otherwise dirty memory could be read. - function _unsafeExtractReference(bytes memory chainBuffer, uint8 offset) private pure returns (bytes32 ref) { - assembly ("memory-safe") { - ref := mload(add(chainBuffer, add(0x20, offset))) - } + uint256 pos = buffer.find(":"); + return (string(buffer.slice(0, pos)), string(buffer.slice(pos + 1))); } } diff --git a/test/crosschain/utils/CAIP.test.js b/test/crosschain/utils/CAIP.test.js new file mode 100644 index 00000000..6dddc655 --- /dev/null +++ b/test/crosschain/utils/CAIP.test.js @@ -0,0 +1,67 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + +const format = (...args) => args.join(':'); + +const SAMPLES = [].concat( + ['1', '56', '137', '43114', '250', '1284', '1313161554', '42161', '10', '8453', '5000', '42220', '2222', '314', '59144', '2031', '534352', '13371', '252', '81457'].map(reference => ({ + namespace: 'eip155', + reference, + account: ethers.Wallet.createRandom().address, // random address + })), + [ 'axelar-dojo-1', 'osmosis-1', 'cosmoshub-4', 'juno-1', 'emoney-3', 'injective-1', 'crescent-1', 'kaiyo-1', 'secret-4', 'secret-4', 'pacific-1', 'stargaze-1', 'mantle-1', 'fetchhub-4', 'kichain-2', 'evmos_9001-2', 'xstaxy-1', 'comdex-1', 'core-1', 'regen-1', 'umee-1', 'agoric-3', 'dimension_37-1', 'acre_9052-1', 'stride-1', 'carbon-1', 'sommelier-3', 'neutron-1', 'reb_1111-1', 'archway-1', 'pio-mainnet-1', 'ixo-5', 'migaloo-1', 'teritori-1', 'haqq_11235-1', 'celestia', 'agamotto', 'chihuahua-1', 'ssc-1', 'dymension_1100-1', 'fxcore', 'perun-1', 'bitsong-2b', 'pirin-1', 'lava-mainnet-1', 'phoenix-1', 'columbus-5' ].map(reference => ({ + namespace: 'cosmos', + reference, + account: ethers.encodeBase58(ethers.randomBytes(32)), // random base58 string + })), +).map(entry => Object.assign(entry, { caip2: format(entry.namespace, entry.reference), caip10: format(entry.namespace, entry.reference, entry.account) })); + +async function fixture() { + const caip2 = await ethers.deployContract('$CAIP2'); + const caip10 = await ethers.deployContract('$CAIP10'); + const { chainId } = await ethers.provider.getNetwork(); + return { caip2, caip10, chainId }; +} + +describe('CAIP utilities', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('CAIP-2', function () { + it('format()', async function () { + expect(await this.caip2.$format()).to.equal(format('eip155', this.chainId)); + }); + + for (const { namespace, reference, caip2 } of SAMPLES) + it (`format(${namespace}, ${reference})`, async function () { + expect(await this.caip2.$format(namespace, reference)).to.equal(caip2); + }); + + for (const { namespace, reference, caip2 } of SAMPLES) + it(`parse(${caip2})`, async function () { + expect(await this.caip2.$parse(caip2)).to.deep.equal([ namespace, reference ]); + }); + }); + + describe('CAIP-10', function () { + const { address: account } = ethers.Wallet.createRandom(); + + it(`format(${account})`, async function () { + // lowercase encoding for now + expect(await this.caip10.$format(ethers.Typed.address(account))).to.equal(format('eip155', this.chainId, account.toLowerCase())); + }); + + for (const { account, caip2, caip10 } of SAMPLES) + it (`format(${caip2}, ${account})`, async function () { + expect(await this.caip10.$format(ethers.Typed.string(caip2), ethers.Typed.string(account))).to.equal(caip10); + }); + + for (const { account, caip2, caip10 } of SAMPLES) + it(`parse(${caip10})`, async function () { + expect(await this.caip10.$parse(caip10)).to.deep.equal([ caip2, account ]); + }); + }); + +}); From 4cdf242fbfa710316f3ebdb328625b70b6957677 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 11 Sep 2024 11:25:18 +0200 Subject: [PATCH 28/51] up --- test/crosschain/chains.js | 93 ++++++++++++++++++++++++++++++ test/crosschain/utils/CAIP.test.js | 23 ++------ 2 files changed, 98 insertions(+), 18 deletions(-) create mode 100644 test/crosschain/chains.js diff --git a/test/crosschain/chains.js b/test/crosschain/chains.js new file mode 100644 index 00000000..be15cb92 --- /dev/null +++ b/test/crosschain/chains.js @@ -0,0 +1,93 @@ +const { ethers } = require('hardhat'); + +const mapValues = (obj, fn) => Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)])); +const format = (...args) => args.join(':'); + +// EVM (https://axelarscan.io/resources/chains?type=evm) +const ethereum = { + 'Ethereum': '1', + 'optimism': '10', + 'binance': '56', + 'Polygon': '137', + 'Fantom': '250', + 'fraxtal': '252', + 'filecoin': '314', + 'Moonbeam': '1284', + 'centrifuge': '2031', + 'kava': '2222', + 'mantle': '5000', + 'base': '8453', + 'immutable': '13371', + 'arbitrum': '42161', + 'celo': '42220', + 'Avalanche': '43114', + 'linea': '59144', + 'blast': '81457', + 'scroll': '534352', + 'aurora': '1313161554', +}; + +// Cosmos (https://axelarscan.io/resources/chains?type=cosmos) +const cosmos = { + 'Axelarnet': 'axelar-dojo-1', + 'osmosis': 'osmosis-1', + 'cosmoshub': 'cosmoshub-4', + 'juno': 'juno-1', + 'e-money': 'emoney-3', + 'injective': 'injective-1', + 'crescent': 'crescent-1', + 'kujira': 'kaiyo-1', + 'secret-snip': 'secret-4', + 'secret': 'secret-4', + 'sei': 'pacific-1', + 'stargaze': 'stargaze-1', + 'assetmantle': 'mantle-1', + 'fetch': 'fetchhub-4', + 'ki': 'kichain-2', + 'evmos': 'evmos_9001-2', + 'aura': 'xstaxy-1', + 'comdex': 'comdex-1', + 'persistence': 'core-1', + 'regen': 'regen-1', + 'umee': 'umee-1', + 'agoric': 'agoric-3', + 'xpla': 'dimension_37-1', + 'acre': 'acre_9052-1', + 'stride': 'stride-1', + 'carbon': 'carbon-1', + 'sommelier': 'sommelier-3', + 'neutron': 'neutron-1', + 'rebus': 'reb_1111-1', + 'archway': 'archway-1', + 'provenance': 'pio-mainnet-1', + 'ixo': 'ixo-5', + 'migaloo': 'migaloo-1', + 'teritori': 'teritori-1', + 'haqq': 'haqq_11235-1', + 'celestia': 'celestia', + 'ojo': 'agamotto', + 'chihuahua': 'chihuahua-1', + 'saga': 'ssc-1', + 'dymension': 'dymension_1100-1', + 'fxcore': 'fxcore', + 'c4e': 'perun-1', + 'bitsong': 'bitsong-2b', + 'nolus': 'pirin-1', + 'lava': 'lava-mainnet-1', + 'terra-2': 'phoenix-1', + 'terra': 'columbus-5', +}; + +module.exports = { + CHAINS: mapValues( + Object.assign( + mapValues(ethereum, reference => ({ namespace: 'eip155', reference, account: ethers.Wallet.createRandom().address })), + mapValues(cosmos, reference => ({ namespace: 'cosmos', reference, account: ethers.encodeBase58(ethers.randomBytes(32)) })), + ), + entry => Object.assign(entry, { + caip2: format(entry.namespace, entry.reference), + caip10: format(entry.namespace, entry.reference, entry.account), + }), + ), + format, +}; diff --git a/test/crosschain/utils/CAIP.test.js b/test/crosschain/utils/CAIP.test.js index 6dddc655..58d0c49a 100644 --- a/test/crosschain/utils/CAIP.test.js +++ b/test/crosschain/utils/CAIP.test.js @@ -2,20 +2,7 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const format = (...args) => args.join(':'); - -const SAMPLES = [].concat( - ['1', '56', '137', '43114', '250', '1284', '1313161554', '42161', '10', '8453', '5000', '42220', '2222', '314', '59144', '2031', '534352', '13371', '252', '81457'].map(reference => ({ - namespace: 'eip155', - reference, - account: ethers.Wallet.createRandom().address, // random address - })), - [ 'axelar-dojo-1', 'osmosis-1', 'cosmoshub-4', 'juno-1', 'emoney-3', 'injective-1', 'crescent-1', 'kaiyo-1', 'secret-4', 'secret-4', 'pacific-1', 'stargaze-1', 'mantle-1', 'fetchhub-4', 'kichain-2', 'evmos_9001-2', 'xstaxy-1', 'comdex-1', 'core-1', 'regen-1', 'umee-1', 'agoric-3', 'dimension_37-1', 'acre_9052-1', 'stride-1', 'carbon-1', 'sommelier-3', 'neutron-1', 'reb_1111-1', 'archway-1', 'pio-mainnet-1', 'ixo-5', 'migaloo-1', 'teritori-1', 'haqq_11235-1', 'celestia', 'agamotto', 'chihuahua-1', 'ssc-1', 'dymension_1100-1', 'fxcore', 'perun-1', 'bitsong-2b', 'pirin-1', 'lava-mainnet-1', 'phoenix-1', 'columbus-5' ].map(reference => ({ - namespace: 'cosmos', - reference, - account: ethers.encodeBase58(ethers.randomBytes(32)), // random base58 string - })), -).map(entry => Object.assign(entry, { caip2: format(entry.namespace, entry.reference), caip10: format(entry.namespace, entry.reference, entry.account) })); +const { CHAINS, format } = require('../chains'); async function fixture() { const caip2 = await ethers.deployContract('$CAIP2'); @@ -34,12 +21,12 @@ describe('CAIP utilities', function () { expect(await this.caip2.$format()).to.equal(format('eip155', this.chainId)); }); - for (const { namespace, reference, caip2 } of SAMPLES) + for (const { namespace, reference, caip2 } of Object.values(CHAINS)) it (`format(${namespace}, ${reference})`, async function () { expect(await this.caip2.$format(namespace, reference)).to.equal(caip2); }); - for (const { namespace, reference, caip2 } of SAMPLES) + for (const { namespace, reference, caip2 } of Object.values(CHAINS)) it(`parse(${caip2})`, async function () { expect(await this.caip2.$parse(caip2)).to.deep.equal([ namespace, reference ]); }); @@ -53,12 +40,12 @@ describe('CAIP utilities', function () { expect(await this.caip10.$format(ethers.Typed.address(account))).to.equal(format('eip155', this.chainId, account.toLowerCase())); }); - for (const { account, caip2, caip10 } of SAMPLES) + for (const { account, caip2, caip10 } of Object.values(CHAINS)) it (`format(${caip2}, ${account})`, async function () { expect(await this.caip10.$format(ethers.Typed.string(caip2), ethers.Typed.string(account))).to.equal(caip10); }); - for (const { account, caip2, caip10 } of SAMPLES) + for (const { account, caip2, caip10 } of Object.values(CHAINS)) it(`parse(${caip10})`, async function () { expect(await this.caip10.$parse(caip10)).to.deep.equal([ caip2, account ]); }); From 40b33a2d36e191a4d019d2b99f277fa5e88d8837 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 16 Sep 2024 15:10:41 +0200 Subject: [PATCH 29/51] using unmerged version of Strings with parsing --- .../axelar/AxelarGatewayDestination.sol | 6 +- .../crosschain/mocks/AxelarGatewayMock.sol | 29 ++ contracts/mocks/AxelarGatewayMock.sol | 15 - contracts/utils/Strings.sol | 374 ++++++++++++++++++ 4 files changed, 405 insertions(+), 19 deletions(-) create mode 100644 contracts/crosschain/mocks/AxelarGatewayMock.sol delete mode 100644 contracts/mocks/AxelarGatewayMock.sol create mode 100644 contracts/utils/Strings.sol diff --git a/contracts/crosschain/axelar/AxelarGatewayDestination.sol b/contracts/crosschain/axelar/AxelarGatewayDestination.sol index 672a9061..8830306a 100644 --- a/contracts/crosschain/axelar/AxelarGatewayDestination.sol +++ b/contracts/crosschain/axelar/AxelarGatewayDestination.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {StringsUnreleased} from "../../utils/Strings.sol"; import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; import {IGatewayDestination} from "../IGatewayDestination.sol"; import {IGatewayDestinationPassive} from "../IGatewayDestinationPassive.sol"; @@ -57,10 +58,7 @@ abstract contract AxelarGatewayDestination is require(CAIP2.format().equal(targetChain), "Invalid tardet chain"); // TODO: not available yet - // address destination = address(uint160(Strings.toUint(targetAccount))); - targetAccount; - address destination = address(0); - + address destination = StringsUnreleased.parseAddress(targetAccount); IGatewayReceiver(destination).receiveMessage(messageId, originChain, originAccount, payload, attributes); emit MessageExecuted(messageId); diff --git a/contracts/crosschain/mocks/AxelarGatewayMock.sol b/contracts/crosschain/mocks/AxelarGatewayMock.sol new file mode 100644 index 00000000..99841d05 --- /dev/null +++ b/contracts/crosschain/mocks/AxelarGatewayMock.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.27; + +import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol"; +import {IAxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarExecutable.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {StringsUnreleased} from "../../utils/Strings.sol"; + +contract AxelarGatewayMock { + using Strings for address; + + function callContract( + string calldata destinationChain, + string calldata contractAddress, + bytes calldata payload + ) external { + // TODO: check that destination chain is local + + emit IAxelarGateway.ContractCall(msg.sender, destinationChain, contractAddress, keccak256(payload), payload); + + address target = StringsUnreleased.parseAddress(contractAddress); + + // NOTE: + // - no commandId in this mock + // - source chain and destination chain are the same in this mock + IAxelarExecutable(target).execute(bytes32(0), destinationChain, msg.sender.toHexString(), payload); + } +} diff --git a/contracts/mocks/AxelarGatewayMock.sol b/contracts/mocks/AxelarGatewayMock.sol deleted file mode 100644 index 27d737b3..00000000 --- a/contracts/mocks/AxelarGatewayMock.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.27; - -contract AxelarGatewayMock { - event CallContract(string destinationChain, string contractAddress, bytes payload); - - function callContract( - string calldata destinationChain, - string calldata contractAddress, - bytes calldata payload - ) external { - emit CallContract(destinationChain, contractAddress, payload); - } -} diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol new file mode 100644 index 00000000..98ceb17d --- /dev/null +++ b/contracts/utils/Strings.sol @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol) + +pragma solidity ^0.8.20; + +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import {SignedMath} from "@openzeppelin/contracts/utils/math/SignedMath.sol"; + +/** + * @dev String operations. + */ +library StringsUnreleased { + using SafeCast for *; + + bytes16 private constant HEX_DIGITS = "0123456789abcdef"; + uint8 private constant ADDRESS_LENGTH = 20; + + /** + * @dev The `value` string doesn't fit in the specified `length`. + */ + error StringsInsufficientHexLength(uint256 value, uint256 length); + + /** + * @dev The string being parsed contains characters that are not in scope of the given base. + */ + error StringsInvalidChar(); + + /** + * @dev The string being parsed is not a properly formated address. + */ + error StringsInvalidAddressFormat(); + + /** + * @dev Converts a `uint256` to its ASCII `string` decimal representation. + */ + function toString(uint256 value) internal pure returns (string memory) { + unchecked { + uint256 length = Math.log10(value) + 1; + string memory buffer = new string(length); + uint256 ptr; + /// @solidity memory-safe-assembly + assembly { + ptr := add(buffer, add(32, length)) + } + while (true) { + ptr--; + /// @solidity memory-safe-assembly + assembly { + mstore8(ptr, byte(mod(value, 10), HEX_DIGITS)) + } + value /= 10; + if (value == 0) break; + } + return buffer; + } + } + + /** + * @dev Converts a `int256` to its ASCII `string` decimal representation. + */ + function toStringSigned(int256 value) internal pure returns (string memory) { + return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value))); + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. + */ + function toHexString(uint256 value) internal pure returns (string memory) { + unchecked { + return toHexString(value, Math.log256(value) + 1); + } + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. + */ + function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { + uint256 localValue = value; + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = HEX_DIGITS[localValue & 0xf]; + localValue >>= 4; + } + if (localValue != 0) { + revert StringsInsufficientHexLength(value, length); + } + return string(buffer); + } + + /** + * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal + * representation. + */ + function toHexString(address addr) internal pure returns (string memory) { + return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH); + } + + /** + * @dev Converts an `address` with fixed length of 20 bytes to its checksummed ASCII `string` hexadecimal + * representation, according to EIP-55. + */ + function toChecksumHexString(address addr) internal pure returns (string memory) { + bytes memory buffer = bytes(toHexString(addr)); + + // hash the hex part of buffer (skip length + 2 bytes, length 40) + uint256 hashValue; + assembly ("memory-safe") { + hashValue := shr(96, keccak256(add(buffer, 0x22), 40)) + } + + for (uint256 i = 41; i > 1; --i) { + // possible values for buffer[i] are 48 (0) to 57 (9) and 97 (a) to 102 (f) + if (hashValue & 0xf > 7 && uint8(buffer[i]) > 96) { + // case shift by xoring with 0x20 + buffer[i] ^= 0x20; + } + hashValue >>= 4; + } + return string(buffer); + } + + /** + * @dev Returns true if the two strings are equal. + */ + function equal(string memory a, string memory b) internal pure returns (bool) { + return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b)); + } + + /** + * @dev Parse a decimal string and returns the value as a `uint256`. + * + * This function will revert if: + * - the string contains any character that is not in [0-9]. + * - the result does not fit in a `uint256`. + */ + function parseUint(string memory input) internal pure returns (uint256) { + return parseUint(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseUint} that parses a substring of `input` located between position `begin` (included) and + * `end` (excluded). + */ + function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) { + (bool success, uint256 value) = tryParseUint(input, begin, end); + if (!success) revert StringsInvalidChar(); + return value; + } + + /** + * @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character. + * + * This function will still revert if the result does not fit in a `uint256` + */ + function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) { + return tryParseUint(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid + * character. + * + * This function will still revert if the result does not fit in a `uint256` + */ + function tryParseUint( + string memory input, + uint256 begin, + uint256 end + ) internal pure returns (bool success, uint256 value) { + bytes memory buffer = bytes(input); + + uint256 result = 0; + for (uint256 i = begin; i < end; ++i) { + uint8 chr = _tryParseChr(buffer[i]); + if (chr > 9) return (false, 0); + result *= 10; + result += chr; + } + return (true, result); + } + + /** + * @dev Parse a decimal string and returns the value as a `int256`. + * + * This function will revert if: + * - the string contains any character (outside the prefix) that is not in [0-9]. + * - the result does not fit in a `int256`. + */ + function parseInt(string memory input) internal pure returns (int256) { + return parseInt(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and + * `end` (excluded). + */ + function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) { + (bool success, int256 value) = tryParseInt(input, begin, end); + if (!success) revert StringsInvalidChar(); + return value; + } + + /** + * @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character. + * + * This function will still revert if the result does not fit in a `int256` + */ + function tryParseInt(string memory input) internal pure returns (bool success, int256 value) { + return tryParseInt(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid + * character. + * + * This function will still revert if the result does not fit in a `int256` + */ + function tryParseInt( + string memory input, + uint256 begin, + uint256 end + ) internal pure returns (bool success, int256 value) { + bytes memory buffer = bytes(input); + + // check presence of a negative sign. + bool isNegative = bytes1(unsafeReadBytesOffset(buffer, begin)) == 0x2d; + int8 factor = isNegative ? int8(-1) : int8(1); + uint256 offset = isNegative ? 1 : 0; + + int256 result = 0; + for (uint256 i = begin + offset; i < end; ++i) { + uint8 chr = _tryParseChr(buffer[i]); + if (chr > 9) return (false, 0); + result *= 10; + result += factor * int8(chr); + } + return (true, result); + } + + /** + * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`. + * + * This function will revert if: + * - the string contains any character (outside the prefix) that is not in [0-9a-fA-F]. + * - the result does not fit in a `uint256`. + */ + function parseHex(string memory input) internal pure returns (uint256) { + return parseHex(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseHex} that parses a substring of `input` located between position `begin` (included) and + * `end` (excluded). + */ + function parseHex(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) { + (bool success, uint256 value) = tryParseHex(input, begin, end); + if (!success) revert StringsInvalidChar(); + return value; + } + + /** + * @dev Variant of {parseHex-string} that returns false if the parsing fails because of an invalid character. + * + * This function will still revert if the result does not fit in a `uint256` + */ + function tryParseHex(string memory input) internal pure returns (bool success, uint256 value) { + return tryParseHex(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseHex-string-uint256-uint256} that returns false if the parsing fails because of an + * invalid character. + * + * This function will still revert if the result does not fit in a `uint256` + */ + function tryParseHex( + string memory input, + uint256 begin, + uint256 end + ) internal pure returns (bool success, uint256 value) { + bytes memory buffer = bytes(input); + + // skip 0x prefix if present + bool hasPrefix = bytes2(unsafeReadBytesOffset(buffer, begin)) == 0x3078; + uint256 offset = hasPrefix ? 2 : 0; + + uint256 result = 0; + for (uint256 i = begin + offset; i < end; ++i) { + uint8 chr = _tryParseChr(buffer[i]); + if (chr > 15) return (false, 0); + result *= 16; + result += chr; + } + return (true, result); + } + + /** + * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`. + * + * This function will revert if: + * - the string is not formated as `(0x)?[0-9a-fA-F]{40}` + */ + function parseAddress(string memory input) internal pure returns (address) { + return parseAddress(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseAddress} that parses a substring of `input` located between position `begin` (included) and + * `end` (excluded). + */ + function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) { + (bool success, address value) = tryParseAddress(input, begin, end); + if (!success) revert StringsInvalidAddressFormat(); + return value; + } + + /** + * @dev Variant of {parseAddress-string} that returns false if the parsing fails because input is not a properly + * formated address. + */ + function tryParseAddress(string memory input) internal pure returns (bool success, address value) { + return tryParseAddress(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly + * formated address. + */ + function tryParseAddress( + string memory input, + uint256 begin, + uint256 end + ) internal pure returns (bool success, address value) { + // check that input is the correct length + bool hasPrefix = bytes2(unsafeReadBytesOffset(bytes(input), begin)) == 0x3078; + uint256 expectedLength = hasPrefix ? 42 : 40; + + if (end - begin == expectedLength) { + // length garantees that this does not overflow, and value2 is at most type(uint160).max + (bool s, uint256 v) = tryParseHex(input, begin, end); + return (s, address(uint160(v))); + } else { + return (false, address(0)); + } + } + + // TODO: documentation. + function unsafeReadBytesOffset(bytes memory buffer, uint256 offset) internal pure returns (bytes32 value) { + assembly ("memory-safe") { + value := mload(add(buffer, add(0x20, offset))) + } + } + + function _tryParseChr(bytes1 chr) private pure returns (uint8) { + uint8 value = uint8(chr); + + // Try to parse `chr`: + // - Case 1: [0-9] + // - Case 2: [a-f] + // - Case 2: [A-F] + // - otherwise not supported + unchecked { + if (value > 47 && value < 58) value -= 48; + else if (value > 96 && value < 103) value -= 87; + else if (value > 64 && value < 71) value -= 55; + else return type(uint8).max; + } + + return value; + } +} From a8ee58bf050d85235f00d2c46ed10a00bd6e28af Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 18 Sep 2024 15:32:35 +0200 Subject: [PATCH 30/51] up --- contracts/crosschain/IGatewayDestination.sol | 7 -- .../crosschain/IGatewayDestinationPassive.sol | 4 +- contracts/crosschain/IGatewayReceiver.sol | 3 +- contracts/crosschain/IGatewaySource.sol | 16 ++-- .../axelar/AxelarGatewayDestination.sol | 95 +++++++++++-------- .../crosschain/axelar/AxelarGatewaySource.sol | 34 ++----- .../crosschain/mocks/AxelarGatewayMock.sol | 69 +++++++++++++- .../crosschain/mocks/GatewayReceiverMock.sol | 46 +++++++++ contracts/utils/Bytes.sol | 89 +++++++++++++---- contracts/utils/CAIP-10.sol | 2 +- contracts/utils/CAIP-2.sol | 2 +- contracts/utils/Strings.sol | 82 +++++++++------- 12 files changed, 309 insertions(+), 140 deletions(-) delete mode 100644 contracts/crosschain/IGatewayDestination.sol create mode 100644 contracts/crosschain/mocks/GatewayReceiverMock.sol diff --git a/contracts/crosschain/IGatewayDestination.sol b/contracts/crosschain/IGatewayDestination.sol deleted file mode 100644 index 74d62c5e..00000000 --- a/contracts/crosschain/IGatewayDestination.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -interface IGatewayDestination { - event MessageExecuted(bytes32 indexed messageId); -} diff --git a/contracts/crosschain/IGatewayDestinationPassive.sol b/contracts/crosschain/IGatewayDestinationPassive.sol index 206ae91d..f42725ae 100644 --- a/contracts/crosschain/IGatewayDestinationPassive.sol +++ b/contracts/crosschain/IGatewayDestinationPassive.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.0; interface IGatewayDestinationPassive { - error GatewayDestinationPassiveInvalidMessage(bytes32 messageDestinationId); + error GatewayDestinationPassiveInvalidMessage(bytes gatewayData); function validateReceivedMessage( - bytes32 messageDestinationId, + bytes calldata gatewayData, string calldata srcChain, string calldata srcAccount, bytes calldata payload, diff --git a/contracts/crosschain/IGatewayReceiver.sol b/contracts/crosschain/IGatewayReceiver.sol index b4f603b9..4395569f 100644 --- a/contracts/crosschain/IGatewayReceiver.sol +++ b/contracts/crosschain/IGatewayReceiver.sol @@ -4,7 +4,8 @@ pragma solidity ^0.8.0; interface IGatewayReceiver { function receiveMessage( - bytes32 messageId, + address gatewayAddr, + bytes calldata gatewayData, string calldata srcChain, string calldata srcAccount, bytes calldata payload, diff --git a/contracts/crosschain/IGatewaySource.sol b/contracts/crosschain/IGatewaySource.sol index 06b374dd..26329d48 100644 --- a/contracts/crosschain/IGatewaySource.sol +++ b/contracts/crosschain/IGatewaySource.sol @@ -3,19 +3,19 @@ pragma solidity ^0.8.0; interface IGatewaySource { - struct Message { - string source; // CAIP-10 account ID - string destination; // CAIP-10 account ID - bytes payload; - bytes[] attributes; - } + event MessageCreated( + bytes32 messageId, + string source, // CAIP-10 account ID + string destination, // CAIP-10 account ID + bytes payload, + bytes[] attributes + ); - event MessageCreated(bytes32 indexed messageId, Message message); event MessageSent(bytes32 indexed messageId); function sendMessage( string calldata destChain, // CAIP-2 chain ID - string calldata destAccount, // i.e. address + string calldata destAccount, // CAIP-10 account ID bytes calldata payload, bytes[] calldata attributes ) external payable returns (bytes32 messageId); diff --git a/contracts/crosschain/axelar/AxelarGatewayDestination.sol b/contracts/crosschain/axelar/AxelarGatewayDestination.sol index 8830306a..c9c2f806 100644 --- a/contracts/crosschain/axelar/AxelarGatewayDestination.sol +++ b/contracts/crosschain/axelar/AxelarGatewayDestination.sol @@ -1,66 +1,87 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.27; import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {StringsUnreleased} from "../../utils/Strings.sol"; import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; -import {IGatewayDestination} from "../IGatewayDestination.sol"; import {IGatewayDestinationPassive} from "../IGatewayDestinationPassive.sol"; import {IGatewayReceiver} from "../IGatewayReceiver.sol"; import {CAIP2} from "../../utils/CAIP-2.sol"; import {CAIP10} from "../../utils/CAIP-10.sol"; -abstract contract AxelarGatewayDestination is - IGatewayDestination, - // IGatewayDestinationPassive, // TODO - AxelarGatewayBase, - AxelarExecutable -{ +abstract contract AxelarGatewayDestination is IGatewayDestinationPassive, AxelarGatewayBase, AxelarExecutable { using Strings for string; + /// @dev Passive mode + function validateReceivedMessage( + bytes calldata gatewayData, + string calldata srcChain, // CAIP-2 + string calldata srcAccount, // CAIP-10 + bytes calldata payload, + bytes[] calldata attributes + ) external virtual override { + // Extract Axelar commandId + bytes32 commandId = abi.decode(gatewayData, (bytes32)); + + // Rebuild expected package + bytes memory package = abi.encode( + CAIP10.format(srcChain, srcAccount), + CAIP10.format(msg.sender), + payload, + attributes + ); + + // Check package was received from remote gateway on src chain + require( + gateway.validateContractCall( + commandId, + fromCAIP2(srcChain), + getRemoteGateway(srcChain), + keccak256(package) + ), + NotApprovedByGateway() + ); + } + + /// @dev Active mode // In this function: - // - `srcChain` is in the Axelar format. It should not be expected to be a proper CAIP-2 format - // - `srcAccount` is the sender of the crosschain message. That should be the remote gateway on the chain which + // - `remoteChain` is in the Axelar format. It should not be expected to be a proper CAIP-2 format + // - `remoteAccount` is the sender of the crosschain message. That should be the remote gateway on the chain which // the message originates from. It is NOT the sender of the crosschain message // // Proper CAIP-10 encoding of the message sender (including the CAIP-2 name of the origin chain can be found in // the message) function _execute( - string calldata srcChain, - string calldata srcAccount, + string calldata remoteChain, // chain of the remote gateway - axelar format + string calldata remoteAccount, // address of the remote gateway bytes calldata package ) internal virtual override { - // Parse the message package - // - message identifier (from the source, not unique ?) - // - source account (caller of this gateway) - // - destination account - // - payload - // - attributes - ( - bytes32 messageId, - string memory caip10Src, - string memory caip10Dst, - bytes memory payload, - bytes[] memory attributes - ) = abi.decode(package, (bytes32, string, string, bytes, bytes[])); + // Parse the package + (string memory srcCAIP10, string memory dstCAIP10, bytes memory payload, bytes[] memory attributes) = abi + .decode(package, (string, string, bytes, bytes[])); - (string memory originChain, string memory originAccount) = CAIP10.parse(caip10Src); - (string memory targetChain, string memory targetAccount) = CAIP10.parse(caip10Dst); + (string memory srcChain, string memory srcAccount) = CAIP10.parse(srcCAIP10); + (string memory dstChain, string memory dstAccount) = CAIP10.parse(dstCAIP10); // check message validity - // - `srcChain` matches origin chain in the message (in caip2) - // - `srcAccount` is the remote gateway on the origin chain. - require(fromCAIP2(originChain).equal(srcChain), "Invalid origin chain"); - require(getRemoteGateway(originChain).equal(srcAccount), "Invalid origin gateway"); + // - `remoteChain` matches origin chain in the message (in caip2) + // - `remoteAccount` is the remote gateway on the origin chain. + require(remoteChain.equal(fromCAIP2(srcChain)), "Invalid origin chain"); + require(remoteAccount.equal(getRemoteGateway(srcChain)), "Invalid origin gateway"); // This check is not required for security. That is enforced by axelar (+ source gateway) - require(CAIP2.format().equal(targetChain), "Invalid tardet chain"); - - // TODO: not available yet - address destination = StringsUnreleased.parseAddress(targetAccount); - IGatewayReceiver(destination).receiveMessage(messageId, originChain, originAccount, payload, attributes); + require(dstChain.equal(CAIP2.format()), "Invalid tardet chain"); - emit MessageExecuted(messageId); + // Active mode + address destination = StringsUnreleased.parseAddress(dstAccount); + IGatewayReceiver(destination).receiveMessage( + address(0), // not needed in active mode + new bytes(0), // not needed in active mode + srcChain, + srcAccount, + payload, + attributes + ); } } diff --git a/contracts/crosschain/axelar/AxelarGatewaySource.sol b/contracts/crosschain/axelar/AxelarGatewaySource.sol index f6d4ae18..6a2d98b3 100644 --- a/contracts/crosschain/axelar/AxelarGatewaySource.sol +++ b/contracts/crosschain/axelar/AxelarGatewaySource.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.27; -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; import {IGatewaySource} from "../IGatewaySource.sol"; import {CAIP10} from "../../utils/CAIP-10.sol"; @@ -17,31 +16,18 @@ abstract contract AxelarGatewaySource is IGatewaySource, AxelarGatewayBase { // TODO: Handle ether (payable) // TODO: Validate attributes - // Validate there's an equivalent chain identifier supported by the gateway - string memory axelarDstChainId = fromCAIP2(dstChain); - require(bytes(axelarDstChainId).length > 0, UnsupportedChain(dstChain)); - string memory caip10Src = CAIP10.format(msg.sender); - string memory caip10Dst = CAIP10.format(dstChain, dstAccount); - string memory remoteGateway = getRemoteGateway(dstChain); - - // Create a message package - // - message identifier (from the source, not unique ?) - // - source account (caller of this gateway) - // - destination account - // - payload - // - attributes - bytes32 messageId = bytes32(0); // TODO: counter ? - bytes memory package = abi.encode(messageId, caip10Src, caip10Dst, payload, attributes); - - // emit event - emit MessageCreated(messageId, Message(caip10Src, caip10Dst, payload, attributes)); + string memory srcCAIP10 = CAIP10.format(msg.sender); + string memory dstCAIP10 = CAIP10.format(dstChain, dstAccount); - // Send the message - localGateway.callContract(axelarDstChainId, remoteGateway, package); + // Create the package + bytes memory package = abi.encode(srcCAIP10, dstCAIP10, payload, attributes); + + // Emit event + emit MessageCreated(0, srcCAIP10, dstCAIP10, payload, attributes); - // TODO - // emit MessageSent(bytes32(0)); + // Send the message + localGateway.callContract(fromCAIP2(dstChain), getRemoteGateway(dstChain), package); - return messageId; + return 0; } } diff --git a/contracts/crosschain/mocks/AxelarGatewayMock.sol b/contracts/crosschain/mocks/AxelarGatewayMock.sol index 99841d05..adf99085 100644 --- a/contracts/crosschain/mocks/AxelarGatewayMock.sol +++ b/contracts/crosschain/mocks/AxelarGatewayMock.sol @@ -4,26 +4,85 @@ pragma solidity ^0.8.27; import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol"; import {IAxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarExecutable.sol"; +import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {StringsUnreleased} from "../../utils/Strings.sol"; -contract AxelarGatewayMock { +contract AxelarGatewayActiveMock { using Strings for address; function callContract( string calldata destinationChain, - string calldata contractAddress, + string calldata destinationContractAddress, bytes calldata payload ) external { // TODO: check that destination chain is local - emit IAxelarGateway.ContractCall(msg.sender, destinationChain, contractAddress, keccak256(payload), payload); - - address target = StringsUnreleased.parseAddress(contractAddress); + emit IAxelarGateway.ContractCall( + msg.sender, + destinationChain, + destinationContractAddress, + keccak256(payload), + payload + ); // NOTE: // - no commandId in this mock // - source chain and destination chain are the same in this mock + address target = StringsUnreleased.parseAddress(destinationContractAddress); IAxelarExecutable(target).execute(bytes32(0), destinationChain, msg.sender.toHexString(), payload); } } + +contract AxelarGatewayPassiveMock { + using Strings for address; + using BitMaps for BitMaps.BitMap; + + BitMaps.BitMap private pendingCommandIds; + + event NewCommandId( + bytes32 indexed commandId, + string destinationChain, + string destinationContractAddress, + bytes payload + ); + + function callContract( + string calldata destinationChain, + string calldata destinationContractAddress, + bytes calldata payload + ) external { + // TODO: check that destination chain is local + + emit IAxelarGateway.ContractCall( + msg.sender, + destinationChain, + destinationContractAddress, + keccak256(payload), + payload + ); + + bytes32 commandId = keccak256( + abi.encode(destinationChain, msg.sender.toHexString(), destinationContractAddress, keccak256(payload)) + ); + + require(!pendingCommandIds.get(uint256(commandId))); + pendingCommandIds.set(uint256(commandId)); + + emit NewCommandId(commandId, destinationChain, destinationContractAddress, payload); + } + + function validateContractCall( + bytes32 commandId, + string calldata sourceChain, + string calldata sourceAddress, + bytes32 payloadHash + ) external returns (bool) { + if (pendingCommandIds.get(uint256(commandId))) { + pendingCommandIds.unset(uint256(commandId)); + + return + commandId == keccak256(abi.encode(sourceChain, sourceAddress, msg.sender.toHexString(), payloadHash)); + } else return false; + } +} diff --git a/contracts/crosschain/mocks/GatewayReceiverMock.sol b/contracts/crosschain/mocks/GatewayReceiverMock.sol new file mode 100644 index 00000000..f0919fcf --- /dev/null +++ b/contracts/crosschain/mocks/GatewayReceiverMock.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.27; + +import {IGatewayDestinationPassive} from "../IGatewayDestinationPassive.sol"; +import {IGatewayReceiver} from "../IGatewayReceiver.sol"; + +contract GatewayReceiverMock is IGatewayReceiver { + address public immutable GATEWAY; + + event MessageReceived(bytes gatewayData, string srcChain, string srcAccount, bytes payload, bytes[] attributes); + + constructor(address _gateway) { + GATEWAY = _gateway; + } + + function isGateway(address instance) public view returns (bool) { + return instance == GATEWAY; + } + + function receiveMessage( + address gatewayAddr, + bytes calldata gatewayData, + string calldata srcChain, + string calldata srcAccount, + bytes calldata payload, + bytes[] calldata attributes + ) public payable { + if (isGateway(msg.sender)) { + // Active mode + // no extra check + } else if (isGateway(gatewayAddr)) { + // Passive mode + IGatewayDestinationPassive(gatewayAddr).validateReceivedMessage( + gatewayData, + srcChain, + srcAccount, + payload, + attributes + ); + } else { + revert("invalid call"); + } + emit MessageReceived(gatewayData, srcChain, srcAccount, payload, attributes); + } +} diff --git a/contracts/utils/Bytes.sol b/contracts/utils/Bytes.sol index 3032b3dc..5b1625dd 100644 --- a/contracts/utils/Bytes.sol +++ b/contracts/utils/Bytes.sol @@ -1,18 +1,32 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.20; + +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; + +/** + * @dev Bytes operations. + */ library Bytes { - /// @dev Forward search for `s` in `buffer` - /// * If `s` is present in the buffer, returns the index of the first instance - /// * If `s` is not present in the buffer, returns the length of the buffer - function find(bytes memory buffer, bytes1 s) internal pure returns (uint256) { - return find(buffer, s, 0); + /** + * @dev Forward search for `s` in `buffer` + * * If `s` is present in the buffer, returns the index of the first instance + * * If `s` is not present in the buffer, returns type(uint256).max + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf + */ + function indexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) { + return indexOf(buffer, s, 0); } - /// @dev Forward search for `s` in `buffer` starting at position `pos` - /// * If `s` is present in the buffer (at or after `pos`), returns the index of the next instance - /// * If `s` is not present in the buffer (at or after `pos`), returns the length of the buffer - function find(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { + /** + * @dev Forward search for `s` in `buffer` starting at position `pos` + * * If `s` is present in the buffer (at or after `pos`), returns the index of the next instance + * * If `s` is not present in the buffer (at or after `pos`), returns the length of the buffer + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf + */ + function indexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { unchecked { uint256 length = buffer.length; for (uint256 i = pos; i < length; ++i) { @@ -20,40 +34,73 @@ library Bytes { return i; } } - return length; + return type(uint256).max; } } - /// @dev Backward search for `s` in `buffer` - /// * If `s` is present in the buffer, returns the index of the last instance - /// * If `s` is not present in the buffer, returns the length of the buffer - function findLastOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) { - return findLastOf(buffer, s, buffer.length); + /** + * @dev Backward search for `s` in `buffer` + * * If `s` is present in the buffer, returns the index of the last instance + * * If `s` is not present in the buffer, returns type(uint256).max + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf + */ + function lastIndexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) { + return lastIndexOf(buffer, s, buffer.length); } - /// @dev Backward search for `s` in `buffer` starting at position `pos` - /// * If `s` is present in the buffer (before `pos`), returns the index of the previous instance - /// * If `s` is not present in the buffer (before `pos`), returns the length of the buffer - function findLastOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { + /** + * @dev Backward search for `s` in `buffer` starting at position `pos` + * * If `s` is present in the buffer (before `pos`), returns the index of the previous instance + * * If `s` is not present in the buffer (before `pos`), returns the length of the buffer + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf + */ + function lastIndexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { unchecked { for (uint256 i = pos; i > 0; --i) { if (buffer[i - 1] == s) { return i - 1; } } - return buffer.length; + return type(uint256).max; } } + /** + * @dev Copies the content of `buffer`, for `start` (included) to the end of `buffer` into a new bytes object in + * memory. + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice + */ function slice(bytes memory buffer, uint256 start) internal pure returns (bytes memory) { return slice(buffer, start, buffer.length); } + /** + * @dev Copies the content of `buffer`, for `start` (included) to the `end` (exluded) into a new bytes object in + * memory. + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice + */ function slice(bytes memory buffer, uint256 start, uint256 end) internal pure returns (bytes memory) { + // sanitize + uint256 length = buffer.length; + start = Math.min(start, length); + end = Math.min(end, length); + // allocate and copy bytes memory result = new bytes(end - start); for (uint256 i = start; i < end; ++i) { result[i - start] = buffer[i]; } return result; } + + /// @dev Reads a bytes32 from a bytes array without bounds checking. + function unsafeReadBytesOffset(bytes memory buffer, uint256 offset) internal pure returns (bytes32 value) { + // This is not memory safe in the general case + assembly { + value := mload(add(buffer, add(0x20, offset))) + } + } } diff --git a/contracts/utils/CAIP-10.sol b/contracts/utils/CAIP-10.sol index 1df51fa0..4c5a30fc 100644 --- a/contracts/utils/CAIP-10.sol +++ b/contracts/utils/CAIP-10.sol @@ -26,7 +26,7 @@ library CAIP10 { function parse(string memory caip10) internal pure returns (string memory caip2, string memory account) { bytes memory buffer = bytes(caip10); - uint256 pos = buffer.findLastOf(":"); + uint256 pos = buffer.lastIndexOf(":"); return (string(buffer.slice(0, pos)), string(buffer.slice(pos + 1))); } } diff --git a/contracts/utils/CAIP-2.sol b/contracts/utils/CAIP-2.sol index 4490e701..9a7c3314 100644 --- a/contracts/utils/CAIP-2.sol +++ b/contracts/utils/CAIP-2.sol @@ -25,7 +25,7 @@ library CAIP2 { function parse(string memory caip2) internal pure returns (string memory namespace, string memory ref) { bytes memory buffer = bytes(caip2); - uint256 pos = buffer.find(":"); + uint256 pos = buffer.indexOf(":"); return (string(buffer.slice(0, pos)), string(buffer.slice(pos + 1))); } } diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index 98ceb17d..6ef483be 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -6,11 +6,13 @@ pragma solidity ^0.8.20; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {SignedMath} from "@openzeppelin/contracts/utils/math/SignedMath.sol"; +import {Bytes} from "./Bytes.sol"; /** * @dev String operations. */ library StringsUnreleased { + using Bytes for bytes; using SafeCast for *; bytes16 private constant HEX_DIGITS = "0123456789abcdef"; @@ -27,7 +29,7 @@ library StringsUnreleased { error StringsInvalidChar(); /** - * @dev The string being parsed is not a properly formated address. + * @dev The string being parsed is not a properly formatted address. */ error StringsInvalidAddressFormat(); @@ -132,9 +134,9 @@ library StringsUnreleased { /** * @dev Parse a decimal string and returns the value as a `uint256`. * - * This function will revert if: - * - the string contains any character that is not in [0-9]. - * - the result does not fit in a `uint256`. + * Requirements: + * - The string must be formatted as `[0-9]*` + * - The result must fit into an `uint256` type */ function parseUint(string memory input) internal pure returns (uint256) { return parseUint(input, 0, bytes(input).length); @@ -143,6 +145,10 @@ library StringsUnreleased { /** * @dev Variant of {parseUint} that parses a substring of `input` located between position `begin` (included) and * `end` (excluded). + * + * Requirements: + * - The substring must be formatted as `[0-9]*` + * - The result must fit into an `uint256` type */ function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) { (bool success, uint256 value) = tryParseUint(input, begin, end); @@ -153,7 +159,8 @@ library StringsUnreleased { /** * @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character. * - * This function will still revert if the result does not fit in a `uint256` + * Requirements: + * - The result must fit into an `uint256` type. */ function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) { return tryParseUint(input, 0, bytes(input).length); @@ -163,7 +170,8 @@ library StringsUnreleased { * @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid * character. * - * This function will still revert if the result does not fit in a `uint256` + * Requirements: + * - The result must fit into an `uint256` type. */ function tryParseUint( string memory input, @@ -185,9 +193,9 @@ library StringsUnreleased { /** * @dev Parse a decimal string and returns the value as a `int256`. * - * This function will revert if: - * - the string contains any character (outside the prefix) that is not in [0-9]. - * - the result does not fit in a `int256`. + * Requirements: + * - The string must be formatted as `[-+]?[0-9]*` + * - The result must fit in an `int256` type. */ function parseInt(string memory input) internal pure returns (int256) { return parseInt(input, 0, bytes(input).length); @@ -196,6 +204,10 @@ library StringsUnreleased { /** * @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and * `end` (excluded). + * + * Requirements: + * - The substring must be formatted as `[-+]?[0-9]*` + * - The result must fit in an `int256` type. */ function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) { (bool success, int256 value) = tryParseInt(input, begin, end); @@ -206,7 +218,8 @@ library StringsUnreleased { /** * @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character. * - * This function will still revert if the result does not fit in a `int256` + * Requirements: + * - The result must fit into an `int256` type. */ function tryParseInt(string memory input) internal pure returns (bool success, int256 value) { return tryParseInt(input, 0, bytes(input).length); @@ -226,9 +239,10 @@ library StringsUnreleased { bytes memory buffer = bytes(input); // check presence of a negative sign. - bool isNegative = bytes1(unsafeReadBytesOffset(buffer, begin)) == 0x2d; - int8 factor = isNegative ? int8(-1) : int8(1); - uint256 offset = isNegative ? 1 : 0; + bool positiveSign = bytes1(buffer.unsafeReadBytesOffset(begin)) == bytes1("+"); + bool negativeSign = bytes1(buffer.unsafeReadBytesOffset(begin)) == bytes1("-"); + uint256 offset = (positiveSign || negativeSign) ? 1 : 0; + int8 factor = negativeSign ? int8(-1) : int8(1); int256 result = 0; for (uint256 i = begin + offset; i < end; ++i) { @@ -243,9 +257,9 @@ library StringsUnreleased { /** * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`. * - * This function will revert if: - * - the string contains any character (outside the prefix) that is not in [0-9a-fA-F]. - * - the result does not fit in a `uint256`. + * Requirements: + * - The string must be formatted as `(0x)?[0-9a-fA-F]*` + * - The result must fit in an `uint256` type. */ function parseHex(string memory input) internal pure returns (uint256) { return parseHex(input, 0, bytes(input).length); @@ -254,6 +268,10 @@ library StringsUnreleased { /** * @dev Variant of {parseHex} that parses a substring of `input` located between position `begin` (included) and * `end` (excluded). + * + * Requirements: + * - The substring must be formatted as `(0x)?[0-9a-fA-F]*` + * - The result must fit in an `uint256` type. */ function parseHex(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) { (bool success, uint256 value) = tryParseHex(input, begin, end); @@ -264,7 +282,8 @@ library StringsUnreleased { /** * @dev Variant of {parseHex-string} that returns false if the parsing fails because of an invalid character. * - * This function will still revert if the result does not fit in a `uint256` + * Requirements: + * - The result must fit into an `uint256` type. */ function tryParseHex(string memory input) internal pure returns (bool success, uint256 value) { return tryParseHex(input, 0, bytes(input).length); @@ -274,7 +293,8 @@ library StringsUnreleased { * @dev Variant of {parseHex-string-uint256-uint256} that returns false if the parsing fails because of an * invalid character. * - * This function will still revert if the result does not fit in a `uint256` + * Requirements: + * - The result must fit into an `uint256` type. */ function tryParseHex( string memory input, @@ -284,7 +304,7 @@ library StringsUnreleased { bytes memory buffer = bytes(input); // skip 0x prefix if present - bool hasPrefix = bytes2(unsafeReadBytesOffset(buffer, begin)) == 0x3078; + bool hasPrefix = bytes2(buffer.unsafeReadBytesOffset(begin)) == bytes2("0x"); uint256 offset = hasPrefix ? 2 : 0; uint256 result = 0; @@ -300,8 +320,8 @@ library StringsUnreleased { /** * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`. * - * This function will revert if: - * - the string is not formated as `(0x)?[0-9a-fA-F]{40}` + * Requirements: + * - The string must be formatted as `(0x)?[0-9a-fA-F]{40}` */ function parseAddress(string memory input) internal pure returns (address) { return parseAddress(input, 0, bytes(input).length); @@ -310,6 +330,9 @@ library StringsUnreleased { /** * @dev Variant of {parseAddress} that parses a substring of `input` located between position `begin` (included) and * `end` (excluded). + * + * Requirements: + * - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}` */ function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) { (bool success, address value) = tryParseAddress(input, begin, end); @@ -318,8 +341,8 @@ library StringsUnreleased { } /** - * @dev Variant of {parseAddress-string} that returns false if the parsing fails because input is not a properly - * formated address. + * @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly + * formatted address. See {parseAddress} requirements. */ function tryParseAddress(string memory input) internal pure returns (bool success, address value) { return tryParseAddress(input, 0, bytes(input).length); @@ -327,7 +350,7 @@ library StringsUnreleased { /** * @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly - * formated address. + * formatted address. See {parseAddress} requirements. */ function tryParseAddress( string memory input, @@ -335,7 +358,7 @@ library StringsUnreleased { uint256 end ) internal pure returns (bool success, address value) { // check that input is the correct length - bool hasPrefix = bytes2(unsafeReadBytesOffset(bytes(input), begin)) == 0x3078; + bool hasPrefix = bytes2(bytes(input).unsafeReadBytesOffset(begin)) == 0x3078; uint256 expectedLength = hasPrefix ? 42 : 40; if (end - begin == expectedLength) { @@ -347,20 +370,13 @@ library StringsUnreleased { } } - // TODO: documentation. - function unsafeReadBytesOffset(bytes memory buffer, uint256 offset) internal pure returns (bytes32 value) { - assembly ("memory-safe") { - value := mload(add(buffer, add(0x20, offset))) - } - } - function _tryParseChr(bytes1 chr) private pure returns (uint8) { uint8 value = uint8(chr); // Try to parse `chr`: // - Case 1: [0-9] // - Case 2: [a-f] - // - Case 2: [A-F] + // - Case 3: [A-F] // - otherwise not supported unchecked { if (value > 47 && value < 58) value -= 48; From 91cb0ac1860a433769da345e4fbc41dfe84a0021 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 18 Sep 2024 16:53:55 +0200 Subject: [PATCH 31/51] workflow testing (active and passive) --- .../crosschain/mocks/AxelarGatewayMock.sol | 46 ++++----- test/crosschain/axelar/AxelarGateway.test.js | 98 +++++++++++++++++++ .../axelar/AxelarGatewayBase.test.js | 67 ------------- .../axelar/AxelarGatewayDestination.test.js | 18 ---- .../axelar/AxelarGatewaySource.test.js | 18 ---- 5 files changed, 115 insertions(+), 132 deletions(-) create mode 100644 test/crosschain/axelar/AxelarGateway.test.js delete mode 100644 test/crosschain/axelar/AxelarGatewayBase.test.js delete mode 100644 test/crosschain/axelar/AxelarGatewayDestination.test.js delete mode 100644 test/crosschain/axelar/AxelarGatewaySource.test.js diff --git a/contracts/crosschain/mocks/AxelarGatewayMock.sol b/contracts/crosschain/mocks/AxelarGatewayMock.sol index adf99085..6c2424f8 100644 --- a/contracts/crosschain/mocks/AxelarGatewayMock.sol +++ b/contracts/crosschain/mocks/AxelarGatewayMock.sol @@ -8,45 +8,24 @@ import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {StringsUnreleased} from "../../utils/Strings.sol"; -contract AxelarGatewayActiveMock { - using Strings for address; - - function callContract( - string calldata destinationChain, - string calldata destinationContractAddress, - bytes calldata payload - ) external { - // TODO: check that destination chain is local - - emit IAxelarGateway.ContractCall( - msg.sender, - destinationChain, - destinationContractAddress, - keccak256(payload), - payload - ); - - // NOTE: - // - no commandId in this mock - // - source chain and destination chain are the same in this mock - address target = StringsUnreleased.parseAddress(destinationContractAddress); - IAxelarExecutable(target).execute(bytes32(0), destinationChain, msg.sender.toHexString(), payload); - } -} - -contract AxelarGatewayPassiveMock { +contract AxelarGatewayMock { using Strings for address; using BitMaps for BitMaps.BitMap; + bool private activeMode; BitMaps.BitMap private pendingCommandIds; - event NewCommandId( + event CommandIdPending( bytes32 indexed commandId, string destinationChain, string destinationContractAddress, bytes payload ); + function setActive(bool enabled) public { + activeMode = enabled; + } + function callContract( string calldata destinationChain, string calldata destinationContractAddress, @@ -69,7 +48,14 @@ contract AxelarGatewayPassiveMock { require(!pendingCommandIds.get(uint256(commandId))); pendingCommandIds.set(uint256(commandId)); - emit NewCommandId(commandId, destinationChain, destinationContractAddress, payload); + emit CommandIdPending(commandId, destinationChain, destinationContractAddress, payload); + + if (activeMode) { + // NOTE: + // - source chain and destination chain are the same in this mock + address target = StringsUnreleased.parseAddress(destinationContractAddress); + IAxelarExecutable(target).execute(commandId, destinationChain, msg.sender.toHexString(), payload); + } } function validateContractCall( @@ -81,6 +67,8 @@ contract AxelarGatewayPassiveMock { if (pendingCommandIds.get(uint256(commandId))) { pendingCommandIds.unset(uint256(commandId)); + emit IAxelarGateway.ContractCallExecuted(commandId); + return commandId == keccak256(abi.encode(sourceChain, sourceAddress, msg.sender.toHexString(), payloadHash)); } else return false; diff --git a/test/crosschain/axelar/AxelarGateway.test.js b/test/crosschain/axelar/AxelarGateway.test.js new file mode 100644 index 00000000..a05d0ddd --- /dev/null +++ b/test/crosschain/axelar/AxelarGateway.test.js @@ -0,0 +1,98 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs'); + +const getAddress = account => (account.target ?? account.address ?? account).toLowerCase(); + +async function fixture() { + const [owner, sender, ...accounts] = await ethers.getSigners(); + + const { chainId } = await ethers.provider.getNetwork(); + const CAIP2 = `eip155:${chainId}`; + const asCAIP10 = account => `eip155:${chainId}:${getAddress(account)}`; + + const axelar = await ethers.deployContract('$AxelarGatewayMock'); + const srcGateway = await ethers.deployContract('$AxelarGatewaySource', [ owner, axelar ]); + const dstGateway = await ethers.deployContract('$AxelarGatewayDestination', [ owner, axelar, axelar ]); + const receiver = await ethers.deployContract('$GatewayReceiverMock', [ dstGateway ]); + + await srcGateway.registerCAIP2Equivalence(CAIP2, 'local'); + await dstGateway.registerCAIP2Equivalence(CAIP2, 'local'); + await srcGateway.registerRemoteGateway(CAIP2, getAddress(dstGateway)); + await dstGateway.registerRemoteGateway(CAIP2, getAddress(srcGateway)); + + return { owner, sender, accounts, CAIP2, asCAIP10, axelar, srcGateway, dstGateway, receiver }; +} + +describe('AxelarGateway', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + it('initial setup', async function () { + expect(await this.srcGateway.localGateway()).to.equal(this.axelar); + expect(await this.srcGateway.fromCAIP2(this.CAIP2)).to.equal('local'); + expect(await this.srcGateway.getRemoteGateway(this.CAIP2)).to.equal(getAddress(this.dstGateway)); + + expect(await this.dstGateway.localGateway()).to.equal(this.axelar); + expect(await this.dstGateway.fromCAIP2(this.CAIP2)).to.equal('local'); + expect(await this.dstGateway.getRemoteGateway(this.CAIP2)).to.equal(getAddress(this.srcGateway)); + }); + + describe('Active mode', function () { + beforeEach(async function () { + await this.axelar.setActive(true); + }); + + it('workflow', async function () { + const srcCAIP10 = this.asCAIP10(this.sender); + const dstCAIP10 = this.asCAIP10(this.receiver); + const payload = ethers.randomBytes(128); + const attributes = []; + const package = ethers.AbiCoder.defaultAbiCoder().encode([ 'string', 'string', 'bytes', 'bytes[]' ], [ srcCAIP10, dstCAIP10, payload, attributes ]); + + const tx = await this.srcGateway.connect(this.sender).sendMessage(this.CAIP2, getAddress(this.receiver), payload, attributes); + await expect(tx) + .to.emit(this.srcGateway, 'MessageCreated').withArgs(ethers.ZeroHash, srcCAIP10, dstCAIP10, payload, attributes) + .to.emit(this.axelar, 'ContractCall').withArgs(this.srcGateway, 'local', getAddress(this.dstGateway), ethers.keccak256(package), package) + .to.emit(this.axelar, 'ContractCallExecuted').withArgs(anyValue) + .to.emit(this.receiver, 'MessageReceived').withArgs(anyValue, this.CAIP2, getAddress(this.sender), payload, attributes); + }); + }); + + describe('Passive mode', function () { + beforeEach(async function () { + await this.axelar.setActive(false); + }); + + it('workflow', async function () { + const srcCAIP10 = this.asCAIP10(this.sender); + const dstCAIP10 = this.asCAIP10(this.receiver); + const payload = ethers.randomBytes(128); + const attributes = []; + const package = ethers.AbiCoder.defaultAbiCoder().encode([ 'string', 'string', 'bytes', 'bytes[]' ], [ srcCAIP10, dstCAIP10, payload, attributes ]); + + const tx = await this.srcGateway.connect(this.sender).sendMessage(this.CAIP2, getAddress(this.receiver), payload, attributes); + await expect(tx) + .to.emit(this.srcGateway, 'MessageCreated').withArgs(ethers.ZeroHash, srcCAIP10, dstCAIP10, payload, attributes) + .to.emit(this.axelar, 'ContractCall').withArgs(this.srcGateway, 'local', getAddress(this.dstGateway), ethers.keccak256(package), package) + .to.emit(this.axelar, 'CommandIdPending').withArgs(anyValue, 'local', getAddress(this.dstGateway), package); + + const { logs } = await tx.wait(); + const commandIdEvent = logs.find(({ address, topics }) => address == this.axelar.target && topics[0] == this.axelar.interface.getEvent('CommandIdPending').topicHash); + const [ commandId ] = this.axelar.interface.decodeEventLog('CommandIdPending', commandIdEvent.data, commandIdEvent.topics); + + await expect(this.receiver.receiveMessage( + this.dstGateway, + commandId, // bytes32 is already self-encoded + this.CAIP2, + getAddress(this.sender), + payload, + attributes, + )) + .to.emit(this.axelar, 'ContractCallExecuted').withArgs(commandId) + .to.emit(this.receiver, 'MessageReceived').withArgs(commandId, this.CAIP2, getAddress(this.sender), payload, attributes); + }); + }); +}); diff --git a/test/crosschain/axelar/AxelarGatewayBase.test.js b/test/crosschain/axelar/AxelarGatewayBase.test.js deleted file mode 100644 index bb3af846..00000000 --- a/test/crosschain/axelar/AxelarGatewayBase.test.js +++ /dev/null @@ -1,67 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const { toBeHex, hexlify } = require('ethers'); - -async function fixture() { - const [owner, other] = await ethers.getSigners(); - - const localGateway = await ethers.deployContract('AxelarGatewayMock'); - const mock = await ethers.deployContract('$AxelarGatewayBase', [owner, localGateway]); - return { owner, other, mock }; -} - -describe('AxelarGatewayBase', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('fromCAIP2', function () { - it('returns empty string if the chain is not supported', async function () { - expect(await this.mock['fromCAIP2(string)']('eip155:11155111')).to.equal(''); - }); - - it('returns the chain name if the chain is supported', async function () { - await this.mock.connect(this.owner).registerCAIP2Equivalence('eip155:1', 'Ethereum'); - expect(await this.mock['fromCAIP2(string)']('eip155:1')).to.equal('Ethereum'); - }); - }); - - describe('getRemoteGateway', function () { - it('returns empty string if there is no remote gateway', async function () { - expect(await this.mock.getRemoteGateway('unknown:unknown')).to.equal(''); - }); - - it('returns the remote gateway if it exists', async function () { - await this.mock.connect(this.owner).registerRemoteGateway('eip155:1', this.other.address); - expect(await this.mock.getRemoteGateway('eip155:1')).to.equal(this.other.address); - }); - }); - - describe('registerCAIP2Equivalence', function () { - it('emits an event', async function () { - await expect(this.mock.connect(this.owner).registerCAIP2Equivalence('eip155:1', 'Ethereum')) - .to.emit(this.mock, 'RegisteredCAIP2Equivalence') - .withArgs('eip155:1', 'Ethereum'); - }); - - it('reverts if the chain is already registered', async function () { - await this.mock.connect(this.owner).registerCAIP2Equivalence('eip155:1', 'Ethereum'); - await expect(this.mock.connect(this.owner).registerCAIP2Equivalence('eip155:1', 'Ethereum')).to.be.reverted; - }); - }); - - describe('registerRemoteGateway', function () { - it('emits an event', async function () { - await expect(this.mock.connect(this.owner).registerRemoteGateway('eip155:1', this.other.address)) - .to.emit(this.mock, 'RegisteredRemoteGateway') - .withArgs('eip155:1', this.other.address); - }); - - it('reverts if the chain is already registered', async function () { - await this.mock.connect(this.owner).registerRemoteGateway('eip155:1', this.other.address); // register once - await expect(this.mock.connect(this.owner).registerRemoteGateway('eip155:1', this.other.address)).to.be - .reverted; - }); - }); -}); diff --git a/test/crosschain/axelar/AxelarGatewayDestination.test.js b/test/crosschain/axelar/AxelarGatewayDestination.test.js deleted file mode 100644 index fbaa7d9d..00000000 --- a/test/crosschain/axelar/AxelarGatewayDestination.test.js +++ /dev/null @@ -1,18 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -async function fixture() { - const mock = await ethers.deployContract('$AxelarGatewayDestination'); - return { mock }; -} - -describe('AxelarGatewayDestination', function () { - before(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('_execute', function () { - // TODO: Add tests - }); -}); diff --git a/test/crosschain/axelar/AxelarGatewaySource.test.js b/test/crosschain/axelar/AxelarGatewaySource.test.js deleted file mode 100644 index 016fe60a..00000000 --- a/test/crosschain/axelar/AxelarGatewaySource.test.js +++ /dev/null @@ -1,18 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -async function fixture() { - const mock = await ethers.deployContract('$AxelarGatewaySource'); - return { mock }; -} - -describe('AxelarGatewaySource', function () { - before(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('_execute', function () { - // TODO: Add tests - }); -}); From 80b0a59e92a30ba1db67648e31a96a230fcf7fce Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 19 Sep 2024 16:56:13 +0200 Subject: [PATCH 32/51] update --- contracts/crosschain/ICAIP2Equivalence.sol | 13 ----- contracts/crosschain/IGatewaySource.sol | 22 --------- .../crosschain/axelar/AxelarGatewayBase.sol | 20 ++++---- .../axelar/AxelarGatewayDestination.sol | 48 +++++++++---------- .../crosschain/axelar/AxelarGatewaySource.sol | 30 +++++++----- .../IGatewayDestinationPassive.sol | 8 ++-- .../{ => interfaces}/IGatewayReceiver.sol | 8 ++-- .../crosschain/interfaces/IGatewaySource.sol | 26 ++++++++++ .../crosschain/mocks/GatewayReceiverMock.sol | 4 +- 9 files changed, 89 insertions(+), 90 deletions(-) delete mode 100644 contracts/crosschain/ICAIP2Equivalence.sol delete mode 100644 contracts/crosschain/IGatewaySource.sol rename contracts/crosschain/{ => interfaces}/IGatewayDestinationPassive.sol (55%) rename contracts/crosschain/{ => interfaces}/IGatewayReceiver.sol (60%) create mode 100644 contracts/crosschain/interfaces/IGatewaySource.sol diff --git a/contracts/crosschain/ICAIP2Equivalence.sol b/contracts/crosschain/ICAIP2Equivalence.sol deleted file mode 100644 index 02f49562..00000000 --- a/contracts/crosschain/ICAIP2Equivalence.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -/// @dev Equivalence interface between CAIP-2 chain identifiers and protocol-specific chain identifiers. -/// -/// See https://chainagnostic.org/CAIPs/caip-2[CAIP2]. -interface ICAIP2Equivalence { - error UnsupportedChain(string caip2); - - /// @dev Retrieves the protocol-specific chain identifier equivalent to a CAIP-2 chain identifier. - function fromCAIP2(string memory caip2) external view returns (string memory); -} diff --git a/contracts/crosschain/IGatewaySource.sol b/contracts/crosschain/IGatewaySource.sol deleted file mode 100644 index 26329d48..00000000 --- a/contracts/crosschain/IGatewaySource.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -interface IGatewaySource { - event MessageCreated( - bytes32 messageId, - string source, // CAIP-10 account ID - string destination, // CAIP-10 account ID - bytes payload, - bytes[] attributes - ); - - event MessageSent(bytes32 indexed messageId); - - function sendMessage( - string calldata destChain, // CAIP-2 chain ID - string calldata destAccount, // CAIP-10 account ID - bytes calldata payload, - bytes[] calldata attributes - ) external payable returns (bytes32 messageId); -} diff --git a/contracts/crosschain/axelar/AxelarGatewayBase.sol b/contracts/crosschain/axelar/AxelarGatewayBase.sol index 703ca4f4..c59b47e2 100644 --- a/contracts/crosschain/axelar/AxelarGatewayBase.sol +++ b/contracts/crosschain/axelar/AxelarGatewayBase.sol @@ -4,14 +4,16 @@ pragma solidity ^0.8.0; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol"; -import {ICAIP2Equivalence} from "../ICAIP2Equivalence.sol"; -abstract contract AxelarGatewayBase is ICAIP2Equivalence, Ownable { +abstract contract AxelarGatewayBase is Ownable { event RegisteredRemoteGateway(string caip2, string gatewayAddress); event RegisteredCAIP2Equivalence(string caip2, string destinationChain); + error UnsupportedChain(string caip2); + IAxelarGateway public immutable localGateway; + // TODO: merge these two (and the corresponding setters?) mapping(string caip2 => string remoteGateway) private _remoteGateways; mapping(string caip2 => string destinationChain) private _equivalence; @@ -19,21 +21,23 @@ abstract contract AxelarGatewayBase is ICAIP2Equivalence, Ownable { localGateway = _gateway; } - function fromCAIP2(string memory caip2) public view returns (string memory) { - return _equivalence[caip2]; + function fromCAIP2(string memory caip2) public view virtual returns (string memory axelarName) { + axelarName = _equivalence[caip2]; + require(bytes(axelarName).length > 0, UnsupportedChain(caip2)); } - function getRemoteGateway(string memory caip2) public view returns (string memory remoteGateway) { - return _remoteGateways[caip2]; + function getRemoteGateway(string memory caip2) public view virtual returns (string memory remoteGateway) { + remoteGateway = _remoteGateways[caip2]; + require(bytes(remoteGateway).length > 0, UnsupportedChain(caip2)); } - function registerCAIP2Equivalence(string calldata caip2, string calldata axelarSupported) public onlyOwner { + function registerCAIP2Equivalence(string calldata caip2, string calldata axelarSupported) public virtual onlyOwner { require(bytes(_equivalence[caip2]).length == 0); _equivalence[caip2] = axelarSupported; emit RegisteredCAIP2Equivalence(caip2, axelarSupported); } - function registerRemoteGateway(string calldata caip2, string calldata remoteGateway) public onlyOwner { + function registerRemoteGateway(string calldata caip2, string calldata remoteGateway) public virtual onlyOwner { require(bytes(_remoteGateways[caip2]).length == 0); _remoteGateways[caip2] = remoteGateway; emit RegisteredRemoteGateway(caip2, remoteGateway); diff --git a/contracts/crosschain/axelar/AxelarGatewayDestination.sol b/contracts/crosschain/axelar/AxelarGatewayDestination.sol index c9c2f806..e7d45186 100644 --- a/contracts/crosschain/axelar/AxelarGatewayDestination.sol +++ b/contracts/crosschain/axelar/AxelarGatewayDestination.sol @@ -5,29 +5,29 @@ pragma solidity ^0.8.27; import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {StringsUnreleased} from "../../utils/Strings.sol"; -import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; -import {IGatewayDestinationPassive} from "../IGatewayDestinationPassive.sol"; -import {IGatewayReceiver} from "../IGatewayReceiver.sol"; import {CAIP2} from "../../utils/CAIP-2.sol"; import {CAIP10} from "../../utils/CAIP-10.sol"; +import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; +import {IGatewayDestinationPassive} from "../interfaces/IGatewayDestinationPassive.sol"; +import {IGatewayReceiver} from "../interfaces/IGatewayReceiver.sol"; abstract contract AxelarGatewayDestination is IGatewayDestinationPassive, AxelarGatewayBase, AxelarExecutable { using Strings for string; /// @dev Passive mode function validateReceivedMessage( - bytes calldata gatewayData, - string calldata srcChain, // CAIP-2 - string calldata srcAccount, // CAIP-10 + bytes calldata messageKey, + string calldata source, // CAIP-2 + string calldata sender, // CAIP-10 bytes calldata payload, bytes[] calldata attributes - ) external virtual override { + ) external virtual { // Extract Axelar commandId - bytes32 commandId = abi.decode(gatewayData, (bytes32)); + bytes32 commandId = abi.decode(messageKey, (bytes32)); // Rebuild expected package bytes memory package = abi.encode( - CAIP10.format(srcChain, srcAccount), + CAIP10.format(source, sender), CAIP10.format(msg.sender), payload, attributes @@ -35,12 +35,7 @@ abstract contract AxelarGatewayDestination is IGatewayDestinationPassive, Axelar // Check package was received from remote gateway on src chain require( - gateway.validateContractCall( - commandId, - fromCAIP2(srcChain), - getRemoteGateway(srcChain), - keccak256(package) - ), + gateway.validateContractCall(commandId, fromCAIP2(source), getRemoteGateway(source), keccak256(package)), NotApprovedByGateway() ); } @@ -59,27 +54,28 @@ abstract contract AxelarGatewayDestination is IGatewayDestinationPassive, Axelar bytes calldata package ) internal virtual override { // Parse the package - (string memory srcCAIP10, string memory dstCAIP10, bytes memory payload, bytes[] memory attributes) = abi - .decode(package, (string, string, bytes, bytes[])); + (string memory from, string memory to, bytes memory payload, bytes[] memory attributes) = abi.decode( + package, + (string, string, bytes, bytes[]) + ); - (string memory srcChain, string memory srcAccount) = CAIP10.parse(srcCAIP10); - (string memory dstChain, string memory dstAccount) = CAIP10.parse(dstCAIP10); + (string memory source, string memory sender) = CAIP10.parse(from); + (string memory destination, string memory receiver) = CAIP10.parse(to); // check message validity // - `remoteChain` matches origin chain in the message (in caip2) // - `remoteAccount` is the remote gateway on the origin chain. - require(remoteChain.equal(fromCAIP2(srcChain)), "Invalid origin chain"); - require(remoteAccount.equal(getRemoteGateway(srcChain)), "Invalid origin gateway"); + require(remoteChain.equal(fromCAIP2(source)), "Invalid origin chain"); + require(remoteAccount.equal(getRemoteGateway(source)), "Invalid origin gateway"); // This check is not required for security. That is enforced by axelar (+ source gateway) - require(dstChain.equal(CAIP2.format()), "Invalid tardet chain"); + require(destination.equal(CAIP2.format()), "Invalid tardet chain"); // Active mode - address destination = StringsUnreleased.parseAddress(dstAccount); - IGatewayReceiver(destination).receiveMessage( + IGatewayReceiver(StringsUnreleased.parseAddress(receiver)).receiveMessage( address(0), // not needed in active mode new bytes(0), // not needed in active mode - srcChain, - srcAccount, + source, + sender, payload, attributes ); diff --git a/contracts/crosschain/axelar/AxelarGatewaySource.sol b/contracts/crosschain/axelar/AxelarGatewaySource.sol index 6a2d98b3..eb302180 100644 --- a/contracts/crosschain/axelar/AxelarGatewaySource.sol +++ b/contracts/crosschain/axelar/AxelarGatewaySource.sol @@ -3,30 +3,38 @@ pragma solidity ^0.8.27; import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; -import {IGatewaySource} from "../IGatewaySource.sol"; +import {IGatewaySource} from "../interfaces/IGatewaySource.sol"; import {CAIP10} from "../../utils/CAIP-10.sol"; abstract contract AxelarGatewaySource is IGatewaySource, AxelarGatewayBase { + function supportsAttribute(bytes4 /*signature*/) public view virtual returns (bool) { + return false; + } + function sendMessage( - string calldata dstChain, // CAIP-2 chain ID - string calldata dstAccount, // i.e. address + string calldata destination, // CAIP-2 chain ID + string calldata receiver, // i.e. address bytes calldata payload, bytes[] calldata attributes - ) external payable override returns (bytes32) { - // TODO: Handle ether (payable) - // TODO: Validate attributes + ) external payable virtual returns (bytes32) { + // TODO: add support for value and attributes ? + require(msg.value == 0, "Value not supported"); + for (uint256 i = 0; i < attributes.length; ++i) { + bytes4 signature = bytes4(attributes[i][0:4]); + require(supportsAttribute(signature), UnsuportedAttribute(signature)); + } - string memory srcCAIP10 = CAIP10.format(msg.sender); - string memory dstCAIP10 = CAIP10.format(dstChain, dstAccount); + string memory from = CAIP10.format(msg.sender); + string memory to = CAIP10.format(destination, receiver); // Create the package - bytes memory package = abi.encode(srcCAIP10, dstCAIP10, payload, attributes); + bytes memory package = abi.encode(from, to, payload, attributes); // Emit event - emit MessageCreated(0, srcCAIP10, dstCAIP10, payload, attributes); + emit MessageCreated(0, from, to, payload, attributes); // Send the message - localGateway.callContract(fromCAIP2(dstChain), getRemoteGateway(dstChain), package); + localGateway.callContract(fromCAIP2(destination), getRemoteGateway(destination), package); return 0; } diff --git a/contracts/crosschain/IGatewayDestinationPassive.sol b/contracts/crosschain/interfaces/IGatewayDestinationPassive.sol similarity index 55% rename from contracts/crosschain/IGatewayDestinationPassive.sol rename to contracts/crosschain/interfaces/IGatewayDestinationPassive.sol index f42725ae..87a640a2 100644 --- a/contracts/crosschain/IGatewayDestinationPassive.sol +++ b/contracts/crosschain/interfaces/IGatewayDestinationPassive.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.0; interface IGatewayDestinationPassive { - error GatewayDestinationPassiveInvalidMessage(bytes gatewayData); + error InvalidMessageKey(bytes messageKey); function validateReceivedMessage( - bytes calldata gatewayData, - string calldata srcChain, - string calldata srcAccount, + bytes calldata messageKey, + string calldata source, + string calldata sender, bytes calldata payload, bytes[] calldata attributes ) external; diff --git a/contracts/crosschain/IGatewayReceiver.sol b/contracts/crosschain/interfaces/IGatewayReceiver.sol similarity index 60% rename from contracts/crosschain/IGatewayReceiver.sol rename to contracts/crosschain/interfaces/IGatewayReceiver.sol index 4395569f..d35f7843 100644 --- a/contracts/crosschain/IGatewayReceiver.sol +++ b/contracts/crosschain/interfaces/IGatewayReceiver.sol @@ -4,10 +4,10 @@ pragma solidity ^0.8.0; interface IGatewayReceiver { function receiveMessage( - address gatewayAddr, - bytes calldata gatewayData, - string calldata srcChain, - string calldata srcAccount, + address gateway, + bytes calldata gatewayMessageKey, + string calldata source, + string calldata sender, bytes calldata payload, bytes[] calldata attributes ) external payable; diff --git a/contracts/crosschain/interfaces/IGatewaySource.sol b/contracts/crosschain/interfaces/IGatewaySource.sol new file mode 100644 index 00000000..4762800e --- /dev/null +++ b/contracts/crosschain/interfaces/IGatewaySource.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IGatewaySource { + event MessageCreated( + bytes32 outboxId, + string from, // CAIP-10 account ID + string to, // CAIP-10 account ID + bytes payload, + bytes[] attributes + ); + + event MessageSent(bytes32 indexed outboxId); + + error UnsuportedAttribute(bytes4 signature); + + function supportsAttribute(bytes4 signature) external view returns (bool); + + function sendMessage( + string calldata destination, // CAIP-2 chain ID + string calldata receiver, // CAIP-10 account ID + bytes calldata payload, + bytes[] calldata attributes + ) external payable returns (bytes32 outboxId); +} diff --git a/contracts/crosschain/mocks/GatewayReceiverMock.sol b/contracts/crosschain/mocks/GatewayReceiverMock.sol index f0919fcf..0fcbd89a 100644 --- a/contracts/crosschain/mocks/GatewayReceiverMock.sol +++ b/contracts/crosschain/mocks/GatewayReceiverMock.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.27; -import {IGatewayDestinationPassive} from "../IGatewayDestinationPassive.sol"; -import {IGatewayReceiver} from "../IGatewayReceiver.sol"; +import {IGatewayDestinationPassive} from "../interfaces/IGatewayDestinationPassive.sol"; +import {IGatewayReceiver} from "../interfaces/IGatewayReceiver.sol"; contract GatewayReceiverMock is IGatewayReceiver { address public immutable GATEWAY; From b2ac19d64ef557093bf8fa3c888e1075e8053840 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 20 Sep 2024 14:22:50 +0200 Subject: [PATCH 33/51] up --- .../crosschain/mocks/GatewayReceiverMock.sol | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/contracts/crosschain/mocks/GatewayReceiverMock.sol b/contracts/crosschain/mocks/GatewayReceiverMock.sol index 0fcbd89a..f0a6becf 100644 --- a/contracts/crosschain/mocks/GatewayReceiverMock.sol +++ b/contracts/crosschain/mocks/GatewayReceiverMock.sol @@ -8,7 +8,7 @@ import {IGatewayReceiver} from "../interfaces/IGatewayReceiver.sol"; contract GatewayReceiverMock is IGatewayReceiver { address public immutable GATEWAY; - event MessageReceived(bytes gatewayData, string srcChain, string srcAccount, bytes payload, bytes[] attributes); + event MessageReceived(bytes gatewayMessageKey, string source, string sender, bytes payload, bytes[] attributes); constructor(address _gateway) { GATEWAY = _gateway; @@ -19,28 +19,28 @@ contract GatewayReceiverMock is IGatewayReceiver { } function receiveMessage( - address gatewayAddr, - bytes calldata gatewayData, - string calldata srcChain, - string calldata srcAccount, + address gateway, + bytes calldata gatewayMessageKey, + string calldata source, + string calldata sender, bytes calldata payload, bytes[] calldata attributes ) public payable { if (isGateway(msg.sender)) { // Active mode // no extra check - } else if (isGateway(gatewayAddr)) { + } else if (isGateway(gateway)) { // Passive mode - IGatewayDestinationPassive(gatewayAddr).validateReceivedMessage( - gatewayData, - srcChain, - srcAccount, + IGatewayDestinationPassive(gateway).validateReceivedMessage( + gatewayMessageKey, + source, + sender, payload, attributes ); } else { revert("invalid call"); } - emit MessageReceived(gatewayData, srcChain, srcAccount, payload, attributes); + emit MessageReceived(gatewayMessageKey, source, sender, payload, attributes); } } From 4f0f1f09975edb2f30f3bcedc30fa8d45d5acdcf Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 30 Sep 2024 16:11:46 +0200 Subject: [PATCH 34/51] address PR comments --- .../crosschain/axelar/AxelarGatewayBase.sol | 90 +++---------------- .../axelar/AxelarGatewayDestination.sol | 30 +++---- .../crosschain/axelar/AxelarGatewaySource.sol | 28 ++++-- .../crosschain/interfaces/IGatewaySource.sol | 4 +- contracts/utils/Bytes.sol | 14 +-- test/crosschain/axelar/AxelarGateway.test.js | 12 +-- 6 files changed, 60 insertions(+), 118 deletions(-) diff --git a/contracts/crosschain/axelar/AxelarGatewayBase.sol b/contracts/crosschain/axelar/AxelarGatewayBase.sol index c59b47e2..684c4655 100644 --- a/contracts/crosschain/axelar/AxelarGatewayBase.sol +++ b/contracts/crosschain/axelar/AxelarGatewayBase.sol @@ -7,7 +7,7 @@ import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/ abstract contract AxelarGatewayBase is Ownable { event RegisteredRemoteGateway(string caip2, string gatewayAddress); - event RegisteredCAIP2Equivalence(string caip2, string destinationChain); + event RegisteredChainEquivalence(string caip2, string destinationChain); error UnsupportedChain(string caip2); @@ -15,15 +15,15 @@ abstract contract AxelarGatewayBase is Ownable { // TODO: merge these two (and the corresponding setters?) mapping(string caip2 => string remoteGateway) private _remoteGateways; - mapping(string caip2 => string destinationChain) private _equivalence; + mapping(string caip2OrAxelar => string axelarOfCaip2) private _chainEquivalence; constructor(IAxelarGateway _gateway) { localGateway = _gateway; } - function fromCAIP2(string memory caip2) public view virtual returns (string memory axelarName) { - axelarName = _equivalence[caip2]; - require(bytes(axelarName).length > 0, UnsupportedChain(caip2)); + function getEquivalentChain(string memory input) public view virtual returns (string memory output) { + output = _chainEquivalence[input]; + require(bytes(output).length > 0, UnsupportedChain(input)); } function getRemoteGateway(string memory caip2) public view virtual returns (string memory remoteGateway) { @@ -31,10 +31,11 @@ abstract contract AxelarGatewayBase is Ownable { require(bytes(remoteGateway).length > 0, UnsupportedChain(caip2)); } - function registerCAIP2Equivalence(string calldata caip2, string calldata axelarSupported) public virtual onlyOwner { - require(bytes(_equivalence[caip2]).length == 0); - _equivalence[caip2] = axelarSupported; - emit RegisteredCAIP2Equivalence(caip2, axelarSupported); + function registerChainEquivalence(string calldata caip2, string calldata axelarSupported) public virtual onlyOwner { + require(bytes(_chainEquivalence[caip2]).length == 0); + _chainEquivalence[caip2] = axelarSupported; + _chainEquivalence[axelarSupported] = caip2; + emit RegisteredChainEquivalence(caip2, axelarSupported); } function registerRemoteGateway(string calldata caip2, string calldata remoteGateway) public virtual onlyOwner { @@ -43,74 +44,3 @@ abstract contract AxelarGatewayBase is Ownable { emit RegisteredRemoteGateway(caip2, remoteGateway); } } - -// EVM (https://axelarscan.io/resources/chains?type=evm) -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("1"))] = "Ethereum"; -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("56"))] = "binance"; -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("137"))] = "Polygon"; -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("43114"))] = "Avalanche"; -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("250"))] = "Fantom"; -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("1284"))] = "Moonbeam"; -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("1313161554"))] = "aurora"; -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("42161"))] = "arbitrum"; -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("10"))] = "optimism"; -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("8453"))] = "base"; -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("5000"))] = "mantle"; -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("42220"))] = "celo"; -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("2222"))] = "kava"; -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("314"))] = "filecoin"; -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("59144"))] = "linea"; -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("2031"))] = "centrifuge"; -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("534352"))] = "scroll"; -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("13371"))] = "immutable"; -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("252"))] = "fraxtal"; -// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("81457"))] = "blast"; - -// Cosmos (https://axelarscan.io/resources/chains?type=cosmos) -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('axelar-dojo-1'))] = 'Axelarnet'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('osmosis-1'))] = 'osmosis'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('cosmoshub-4'))] = 'cosmoshub'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('juno-1'))] = 'juno'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('emoney-3'))] = 'e-money'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('injective-1'))] = 'injective'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('crescent-1'))] = 'crescent'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('kaiyo-1'))] = 'kujira'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('secret-4'))] = 'secret-snip'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('secret-4'))] = 'secret'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('pacific-1'))] = 'sei'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('stargaze-1'))] = 'stargaze'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('mantle-1'))] = 'assetmantle'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('fetchhub-4'))] = 'fetch'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('kichain-2'))] = 'ki'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('evmos_9001-2'))] = 'evmos'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('xstaxy-1'))] = 'aura'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('comdex-1'))] = 'comdex'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('core-1'))] = 'persistence'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('regen-1'))] = 'regen'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('umee-1'))] = 'umee'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('agoric-3'))] = 'agoric'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('dimension_37-1'))] = 'xpla'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('acre_9052-1'))] = 'acre'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('stride-1'))] = 'stride'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('carbon-1'))] = 'carbon'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('sommelier-3'))] = 'sommelier'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('neutron-1'))] = 'neutron'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('reb_1111-1'))] = 'rebus'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('archway-1'))] = 'archway'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('pio-mainnet-1'))] = 'provenance'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('ixo-5'))] = 'ixo'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('migaloo-1'))] = 'migaloo'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('teritori-1'))] = 'teritori'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('haqq_11235-1'))] = 'haqq'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('celestia'))] = 'celestia'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('agamotto'))] = 'ojo'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('chihuahua-1'))] = 'chihuahua'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('ssc-1'))] = 'saga'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('dymension_1100-1'))] = 'dymension'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('fxcore'))] = 'fxcore'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('perun-1'))] = 'c4e'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('bitsong-2b'))] = 'bitsong'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('pirin-1'))] = 'nolus'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('lava-mainnet-1'))] = 'lava'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('phoenix-1'))] = 'terra-2'; -// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('columbus-5'))] = 'terra';" diff --git a/contracts/crosschain/axelar/AxelarGatewayDestination.sol b/contracts/crosschain/axelar/AxelarGatewayDestination.sol index e7d45186..e8d3c1ae 100644 --- a/contracts/crosschain/axelar/AxelarGatewayDestination.sol +++ b/contracts/crosschain/axelar/AxelarGatewayDestination.sol @@ -12,6 +12,7 @@ import {IGatewayDestinationPassive} from "../interfaces/IGatewayDestinationPassi import {IGatewayReceiver} from "../interfaces/IGatewayReceiver.sol"; abstract contract AxelarGatewayDestination is IGatewayDestinationPassive, AxelarGatewayBase, AxelarExecutable { + using Strings for address; using Strings for string; /// @dev Passive mode @@ -26,16 +27,21 @@ abstract contract AxelarGatewayDestination is IGatewayDestinationPassive, Axelar bytes32 commandId = abi.decode(messageKey, (bytes32)); // Rebuild expected package - bytes memory package = abi.encode( - CAIP10.format(source, sender), - CAIP10.format(msg.sender), + bytes memory adapterPayload = abi.encode( + sender, + msg.sender.toHexString(), // receiver payload, attributes ); // Check package was received from remote gateway on src chain require( - gateway.validateContractCall(commandId, fromCAIP2(source), getRemoteGateway(source), keccak256(package)), + gateway.validateContractCall( + commandId, + getEquivalentChain(source), + getRemoteGateway(source), + keccak256(adapterPayload) + ), NotApprovedByGateway() ); } @@ -51,24 +57,18 @@ abstract contract AxelarGatewayDestination is IGatewayDestinationPassive, Axelar function _execute( string calldata remoteChain, // chain of the remote gateway - axelar format string calldata remoteAccount, // address of the remote gateway - bytes calldata package + bytes calldata adapterPayload ) internal virtual override { // Parse the package - (string memory from, string memory to, bytes memory payload, bytes[] memory attributes) = abi.decode( - package, + (string memory sender, string memory receiver, bytes memory payload, bytes[] memory attributes) = abi.decode( + adapterPayload, (string, string, bytes, bytes[]) ); - - (string memory source, string memory sender) = CAIP10.parse(from); - (string memory destination, string memory receiver) = CAIP10.parse(to); + string memory source = getEquivalentChain(remoteChain); // check message validity - // - `remoteChain` matches origin chain in the message (in caip2) // - `remoteAccount` is the remote gateway on the origin chain. - require(remoteChain.equal(fromCAIP2(source)), "Invalid origin chain"); - require(remoteAccount.equal(getRemoteGateway(source)), "Invalid origin gateway"); - // This check is not required for security. That is enforced by axelar (+ source gateway) - require(destination.equal(CAIP2.format()), "Invalid tardet chain"); + require(getRemoteGateway(source).equal(remoteAccount), "Invalid origin gateway"); // Active mode IGatewayReceiver(StringsUnreleased.parseAddress(receiver)).receiveMessage( diff --git a/contracts/crosschain/axelar/AxelarGatewaySource.sol b/contracts/crosschain/axelar/AxelarGatewaySource.sol index eb302180..7bcc0eda 100644 --- a/contracts/crosschain/axelar/AxelarGatewaySource.sol +++ b/contracts/crosschain/axelar/AxelarGatewaySource.sol @@ -4,10 +4,14 @@ pragma solidity ^0.8.27; import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; import {IGatewaySource} from "../interfaces/IGatewaySource.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {CAIP2} from "../../utils/CAIP-2.sol"; import {CAIP10} from "../../utils/CAIP-10.sol"; abstract contract AxelarGatewaySource is IGatewaySource, AxelarGatewayBase { - function supportsAttribute(bytes4 /*signature*/) public view virtual returns (bool) { + using Strings for address; + + function supportsAttribute(bytes4 /*selector*/) public view virtual returns (bool) { return false; } @@ -20,21 +24,27 @@ abstract contract AxelarGatewaySource is IGatewaySource, AxelarGatewayBase { // TODO: add support for value and attributes ? require(msg.value == 0, "Value not supported"); for (uint256 i = 0; i < attributes.length; ++i) { - bytes4 signature = bytes4(attributes[i][0:4]); - require(supportsAttribute(signature), UnsuportedAttribute(signature)); + bytes4 selector = bytes4(attributes[i][0:4]); + require(supportsAttribute(selector), UnsuportedAttribute(selector)); } - string memory from = CAIP10.format(msg.sender); - string memory to = CAIP10.format(destination, receiver); - // Create the package - bytes memory package = abi.encode(from, to, payload, attributes); + string memory sender = msg.sender.toHexString(); + bytes memory adapterPayload = abi.encode(sender, receiver, payload, attributes); // Emit event - emit MessageCreated(0, from, to, payload, attributes); + emit MessageCreated( + 0, + CAIP10.format(CAIP2.format(), sender), + CAIP10.format(destination, receiver), + payload, + attributes + ); // Send the message - localGateway.callContract(fromCAIP2(destination), getRemoteGateway(destination), package); + string memory axelarDestination = getEquivalentChain(destination); + string memory remoteGateway = getRemoteGateway(destination); + localGateway.callContract(axelarDestination, remoteGateway, adapterPayload); return 0; } diff --git a/contracts/crosschain/interfaces/IGatewaySource.sol b/contracts/crosschain/interfaces/IGatewaySource.sol index 4762800e..ec11030a 100644 --- a/contracts/crosschain/interfaces/IGatewaySource.sol +++ b/contracts/crosschain/interfaces/IGatewaySource.sol @@ -5,8 +5,8 @@ pragma solidity ^0.8.0; interface IGatewaySource { event MessageCreated( bytes32 outboxId, - string from, // CAIP-10 account ID - string to, // CAIP-10 account ID + string sender, // CAIP-10 account ID + string receiver, // CAIP-10 account ID bytes payload, bytes[] attributes ); diff --git a/contracts/utils/Bytes.sol b/contracts/utils/Bytes.sol index 5b1625dd..6cea6e84 100644 --- a/contracts/utils/Bytes.sol +++ b/contracts/utils/Bytes.sol @@ -46,19 +46,19 @@ library Bytes { * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf */ function lastIndexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) { - return lastIndexOf(buffer, s, buffer.length); + return lastIndexOf(buffer, s, buffer.length - 1); } /** * @dev Backward search for `s` in `buffer` starting at position `pos` - * * If `s` is present in the buffer (before `pos`), returns the index of the previous instance - * * If `s` is not present in the buffer (before `pos`), returns the length of the buffer + * * If `s` is present in the buffer (at or before `pos`), returns the index of the previous instance + * * If `s` is not present in the buffer (at or before `pos`), returns the length of the buffer * * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf */ function lastIndexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { unchecked { - for (uint256 i = pos; i > 0; --i) { + for (uint256 i = pos + 1; i > 0; --i) { if (buffer[i - 1] == s) { return i - 1; } @@ -88,11 +88,13 @@ library Bytes { uint256 length = buffer.length; start = Math.min(start, length); end = Math.min(end, length); + // allocate and copy bytes memory result = new bytes(end - start); - for (uint256 i = start; i < end; ++i) { - result[i - start] = buffer[i]; + assembly ("memory-safe") { + mcopy(add(result, 0x20), add(buffer, add(start, 0x20)), sub(end, start)) } + return result; } diff --git a/test/crosschain/axelar/AxelarGateway.test.js b/test/crosschain/axelar/AxelarGateway.test.js index a05d0ddd..d1ccda71 100644 --- a/test/crosschain/axelar/AxelarGateway.test.js +++ b/test/crosschain/axelar/AxelarGateway.test.js @@ -17,8 +17,8 @@ async function fixture() { const dstGateway = await ethers.deployContract('$AxelarGatewayDestination', [ owner, axelar, axelar ]); const receiver = await ethers.deployContract('$GatewayReceiverMock', [ dstGateway ]); - await srcGateway.registerCAIP2Equivalence(CAIP2, 'local'); - await dstGateway.registerCAIP2Equivalence(CAIP2, 'local'); + await srcGateway.registerChainEquivalence(CAIP2, 'local'); + await dstGateway.registerChainEquivalence(CAIP2, 'local'); await srcGateway.registerRemoteGateway(CAIP2, getAddress(dstGateway)); await dstGateway.registerRemoteGateway(CAIP2, getAddress(srcGateway)); @@ -32,11 +32,11 @@ describe('AxelarGateway', function () { it('initial setup', async function () { expect(await this.srcGateway.localGateway()).to.equal(this.axelar); - expect(await this.srcGateway.fromCAIP2(this.CAIP2)).to.equal('local'); + expect(await this.srcGateway.getEquivalentChain(this.CAIP2)).to.equal('local'); expect(await this.srcGateway.getRemoteGateway(this.CAIP2)).to.equal(getAddress(this.dstGateway)); expect(await this.dstGateway.localGateway()).to.equal(this.axelar); - expect(await this.dstGateway.fromCAIP2(this.CAIP2)).to.equal('local'); + expect(await this.dstGateway.getEquivalentChain(this.CAIP2)).to.equal('local'); expect(await this.dstGateway.getRemoteGateway(this.CAIP2)).to.equal(getAddress(this.srcGateway)); }); @@ -50,7 +50,7 @@ describe('AxelarGateway', function () { const dstCAIP10 = this.asCAIP10(this.receiver); const payload = ethers.randomBytes(128); const attributes = []; - const package = ethers.AbiCoder.defaultAbiCoder().encode([ 'string', 'string', 'bytes', 'bytes[]' ], [ srcCAIP10, dstCAIP10, payload, attributes ]); + const package = ethers.AbiCoder.defaultAbiCoder().encode([ 'string', 'string', 'bytes', 'bytes[]' ], [ getAddress(this.sender), getAddress(this.receiver), payload, attributes ]); const tx = await this.srcGateway.connect(this.sender).sendMessage(this.CAIP2, getAddress(this.receiver), payload, attributes); await expect(tx) @@ -71,7 +71,7 @@ describe('AxelarGateway', function () { const dstCAIP10 = this.asCAIP10(this.receiver); const payload = ethers.randomBytes(128); const attributes = []; - const package = ethers.AbiCoder.defaultAbiCoder().encode([ 'string', 'string', 'bytes', 'bytes[]' ], [ srcCAIP10, dstCAIP10, payload, attributes ]); + const package = ethers.AbiCoder.defaultAbiCoder().encode([ 'string', 'string', 'bytes', 'bytes[]' ], [ getAddress(this.sender), getAddress(this.receiver), payload, attributes ]); const tx = await this.srcGateway.connect(this.sender).sendMessage(this.CAIP2, getAddress(this.receiver), payload, attributes); await expect(tx) From 5a5b76470b50971ba210a37c9a6c05c3ef44b4be Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 30 Sep 2024 16:14:06 +0200 Subject: [PATCH 35/51] renovate --- package-lock.json | 2117 +++++++++++++++++++++++++-------------------- 1 file changed, 1189 insertions(+), 928 deletions(-) diff --git a/package-lock.json b/package-lock.json index 64b808f4..03da57aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,14 +29,14 @@ }, "node_modules/@adraffy/ens-normalize": { "version": "1.10.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", + "dev": true }, "node_modules/@axelar-network/axelar-gmp-sdk-solidity": { "version": "5.10.0", "resolved": "https://registry.npmjs.org/@axelar-network/axelar-gmp-sdk-solidity/-/axelar-gmp-sdk-solidity-5.10.0.tgz", "integrity": "sha512-s8SImALvYB+5AeiT3tbfWNBI2Mhqw1x91i/zM3DNpVUCnAR2HKtsB9T84KnUn/OJjOVgb4h0lv7q9smeYniRPw==", - "license": "MIT", "engines": { "node": ">=18" } @@ -46,7 +46,6 @@ "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-4.0.1.tgz", "integrity": "sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==", "dev": true, - "license": "MPL-2.0", "bin": { "rlp": "bin/rlp" }, @@ -59,7 +58,6 @@ "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-8.1.0.tgz", "integrity": "sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==", "dev": true, - "license": "MPL-2.0", "dependencies": { "@ethereumjs/rlp": "^4.0.1", "ethereum-cryptography": "^2.0.0", @@ -74,7 +72,6 @@ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", "dev": true, - "license": "MIT", "dependencies": { "@noble/hashes": "1.4.0" }, @@ -87,7 +84,6 @@ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 16" }, @@ -95,41 +91,11 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@ethereumjs/util/node_modules/@scure/bip32": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", - "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/curves": "~1.4.0", - "@noble/hashes": "~1.4.0", - "@scure/base": "~1.1.6" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@ethereumjs/util/node_modules/@scure/bip39": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", - "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/hashes": "~1.4.0", - "@scure/base": "~1.1.6" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@ethereumjs/util/node_modules/ethereum-cryptography": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", "dev": true, - "license": "MIT", "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", @@ -139,6 +105,8 @@ }, "node_modules/@ethersproject/abi": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", + "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", "dev": true, "funding": [ { @@ -150,7 +118,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "license": "MIT", "dependencies": { "@ethersproject/address": "^5.7.0", "@ethersproject/bignumber": "^5.7.0", @@ -165,6 +132,8 @@ }, "node_modules/@ethersproject/abstract-provider": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", + "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", "dev": true, "funding": [ { @@ -176,7 +145,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "license": "MIT", "dependencies": { "@ethersproject/bignumber": "^5.7.0", "@ethersproject/bytes": "^5.7.0", @@ -189,6 +157,8 @@ }, "node_modules/@ethersproject/abstract-signer": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", + "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", "dev": true, "funding": [ { @@ -200,7 +170,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "license": "MIT", "dependencies": { "@ethersproject/abstract-provider": "^5.7.0", "@ethersproject/bignumber": "^5.7.0", @@ -211,6 +180,8 @@ }, "node_modules/@ethersproject/address": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", + "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", "dev": true, "funding": [ { @@ -222,7 +193,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "license": "MIT", "dependencies": { "@ethersproject/bignumber": "^5.7.0", "@ethersproject/bytes": "^5.7.0", @@ -233,6 +203,8 @@ }, "node_modules/@ethersproject/base64": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", + "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", "dev": true, "funding": [ { @@ -244,13 +216,14 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "license": "MIT", "dependencies": { "@ethersproject/bytes": "^5.7.0" } }, "node_modules/@ethersproject/bignumber": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", + "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", "dev": true, "funding": [ { @@ -262,7 +235,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "license": "MIT", "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/logger": "^5.7.0", @@ -271,6 +243,8 @@ }, "node_modules/@ethersproject/bytes": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", + "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", "dev": true, "funding": [ { @@ -282,13 +256,14 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "license": "MIT", "dependencies": { "@ethersproject/logger": "^5.7.0" } }, "node_modules/@ethersproject/constants": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", + "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", "dev": true, "funding": [ { @@ -300,13 +275,14 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "license": "MIT", "dependencies": { "@ethersproject/bignumber": "^5.7.0" } }, "node_modules/@ethersproject/hash": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", + "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", "dev": true, "funding": [ { @@ -318,7 +294,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "license": "MIT", "dependencies": { "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/address": "^5.7.0", @@ -333,6 +308,8 @@ }, "node_modules/@ethersproject/keccak256": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", + "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", "dev": true, "funding": [ { @@ -344,7 +321,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "license": "MIT", "dependencies": { "@ethersproject/bytes": "^5.7.0", "js-sha3": "0.8.0" @@ -352,6 +328,8 @@ }, "node_modules/@ethersproject/logger": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", + "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", "dev": true, "funding": [ { @@ -362,11 +340,12 @@ "type": "individual", "url": "https://www.buymeacoffee.com/ricmoo" } - ], - "license": "MIT" + ] }, "node_modules/@ethersproject/networks": { "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", + "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", "dev": true, "funding": [ { @@ -378,13 +357,14 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "license": "MIT", "dependencies": { "@ethersproject/logger": "^5.7.0" } }, "node_modules/@ethersproject/properties": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", + "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", "dev": true, "funding": [ { @@ -396,13 +376,14 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "license": "MIT", "dependencies": { "@ethersproject/logger": "^5.7.0" } }, "node_modules/@ethersproject/rlp": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", + "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", "dev": true, "funding": [ { @@ -414,7 +395,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "license": "MIT", "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/logger": "^5.7.0" @@ -422,6 +402,8 @@ }, "node_modules/@ethersproject/signing-key": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", + "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", "dev": true, "funding": [ { @@ -433,7 +415,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "license": "MIT", "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/logger": "^5.7.0", @@ -445,6 +426,8 @@ }, "node_modules/@ethersproject/strings": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", + "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", "dev": true, "funding": [ { @@ -456,7 +439,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "license": "MIT", "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/constants": "^5.7.0", @@ -465,6 +447,8 @@ }, "node_modules/@ethersproject/transactions": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", + "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", "dev": true, "funding": [ { @@ -476,7 +460,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "license": "MIT", "dependencies": { "@ethersproject/address": "^5.7.0", "@ethersproject/bignumber": "^5.7.0", @@ -491,6 +474,8 @@ }, "node_modules/@ethersproject/web": { "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", + "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", "dev": true, "funding": [ { @@ -502,7 +487,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "license": "MIT", "dependencies": { "@ethersproject/base64": "^5.7.0", "@ethersproject/bytes": "^5.7.0", @@ -513,16 +497,18 @@ }, "node_modules/@fastify/busboy": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", "dev": true, - "license": "MIT", "engines": { "node": ">=14" } }, "node_modules/@metamask/eth-sig-util": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz", + "integrity": "sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==", "dev": true, - "license": "ISC", "dependencies": { "ethereumjs-abi": "^0.6.8", "ethereumjs-util": "^6.2.1", @@ -534,10 +520,41 @@ "node": ">=12.0.0" } }, + "node_modules/@metamask/eth-sig-util/node_modules/@types/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@metamask/eth-sig-util/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/@metamask/eth-sig-util/node_modules/ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "dev": true, + "dependencies": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + }, "node_modules/@noble/curves": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", "dev": true, - "license": "MIT", "dependencies": { "@noble/hashes": "1.3.2" }, @@ -547,8 +564,9 @@ }, "node_modules/@noble/hashes": { "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 16" }, @@ -558,21 +576,21 @@ }, "node_modules/@noble/secp256k1": { "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", + "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==", "dev": true, "funding": [ { "type": "individual", "url": "https://paulmillr.com/funding/" } - ], - "license": "MIT" + ] }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -586,7 +604,6 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, - "license": "MIT", "engines": { "node": ">= 8" } @@ -596,7 +613,6 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -606,49 +622,100 @@ } }, "node_modules/@nomicfoundation/edr": { - "version": "0.3.7", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.6.2.tgz", + "integrity": "sha512-yPUegN3sTWiAkRatCmGRkuvMgD9HSSpivl2ebAqq0aU2xgC7qmIO+YQPxQ3Z46MUoi7MrTf4e6GpbT4S/8x0ew==", "dev": true, - "license": "MIT", + "dependencies": { + "@nomicfoundation/edr-darwin-arm64": "0.6.2", + "@nomicfoundation/edr-darwin-x64": "0.6.2", + "@nomicfoundation/edr-linux-arm64-gnu": "0.6.2", + "@nomicfoundation/edr-linux-arm64-musl": "0.6.2", + "@nomicfoundation/edr-linux-x64-gnu": "0.6.2", + "@nomicfoundation/edr-linux-x64-musl": "0.6.2", + "@nomicfoundation/edr-win32-x64-msvc": "0.6.2" + }, "engines": { "node": ">= 18" - }, - "optionalDependencies": { - "@nomicfoundation/edr-darwin-arm64": "0.3.7", - "@nomicfoundation/edr-darwin-x64": "0.3.7", - "@nomicfoundation/edr-linux-arm64-gnu": "0.3.7", - "@nomicfoundation/edr-linux-arm64-musl": "0.3.7", - "@nomicfoundation/edr-linux-x64-gnu": "0.3.7", - "@nomicfoundation/edr-linux-x64-musl": "0.3.7", - "@nomicfoundation/edr-win32-x64-msvc": "0.3.7" } }, "node_modules/@nomicfoundation/edr-darwin-arm64": { - "version": "0.3.7", - "cpu": [ - "arm64" - ], + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.6.2.tgz", + "integrity": "sha512-o4A9SaPlxJ1MS6u8Ozqq7Y0ri2XO0jASw+qkytQyBYowNFNReoGqVSs7SCwenYCDiN+1il8+M0VAUq7wOovnCQ==", + "dev": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@nomicfoundation/edr-darwin-x64": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.6.2.tgz", + "integrity": "sha512-WG8BeG2eR3rFC+2/9V1hoPGW7tmNRUcuztdHUijO1h2flRsf2YWv+kEHO+EEnhGkEbgBUiwOrwlwlSMxhe2cGA==", + "dev": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@nomicfoundation/edr-linux-arm64-gnu": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.6.2.tgz", + "integrity": "sha512-wvHaTmOwuPjRIOqBB+paI3RBdNlG8f3e1F2zWj75EdeWwefimPzzFUs05JxOYuPO0JhDQIn2tbYUgdZbBQ+mqg==", + "dev": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@nomicfoundation/edr-linux-arm64-musl": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.6.2.tgz", + "integrity": "sha512-UrOAxnsywUcEngQM2ZxIuucci0VX29hYxX7jcpwZU50HICCjxNsxnuXYPxv+IM+6gbhBY1FYvYJGW4PJcP1Nyw==", + "dev": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@nomicfoundation/edr-linux-x64-gnu": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.6.2.tgz", + "integrity": "sha512-gYxlPLi7fkNcmDmCwZWQa5eOfNcTDundE+TWjpyafxLAjodQuKBD4I0p4XbnuocHjoBEeNzLWdE5RShbZEXEJA==", + "dev": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@nomicfoundation/edr-linux-x64-musl": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.6.2.tgz", + "integrity": "sha512-ev5hy9wmiHZi1GKQ1l6PJ2+UpsUh+DvK9AwiCZVEdaicuhmTfO6fdL4szgE4An8RU+Ou9DeiI1tZcq6iw++Wuw==", + "dev": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@nomicfoundation/edr-win32-x64-msvc": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.6.2.tgz", + "integrity": "sha512-2ZXVVcmdmEeX0Hb3IAurHUjgU3H1GIk9h7Okosdjgl3tl+BaNHxi84Us+DblynO1LRj8nL/ATeVtSfBuW3Z1vw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { "node": ">= 18" } }, "node_modules/@nomicfoundation/ethereumjs-common": { "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.4.tgz", + "integrity": "sha512-9Rgb658lcWsjiicr5GzNCjI1llow/7r0k50dLL95OJ+6iZJcVbi15r3Y0xh2cIO+zgX0WIHcbzIu6FeQf9KPrg==", "dev": true, - "license": "MIT", "dependencies": { "@nomicfoundation/ethereumjs-util": "9.0.4" } }, "node_modules/@nomicfoundation/ethereumjs-rlp": { "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.4.tgz", + "integrity": "sha512-8H1S3s8F6QueOc/X92SdrA4RDenpiAEqMg5vJH99kcQaCy/a3Q6fgseo75mgWlbanGJXSlAPtnCeG9jvfTYXlw==", "dev": true, - "license": "MPL-2.0", "bin": { "rlp": "bin/rlp.cjs" }, @@ -658,8 +725,9 @@ }, "node_modules/@nomicfoundation/ethereumjs-tx": { "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.4.tgz", + "integrity": "sha512-Xjv8wAKJGMrP1f0n2PeyfFCCojHd7iS3s/Ab7qzF1S64kxZ8Z22LCMynArYsVqiFx6rzYy548HNVEyI+AYN/kw==", "dev": true, - "license": "MPL-2.0", "dependencies": { "@nomicfoundation/ethereumjs-common": "4.0.4", "@nomicfoundation/ethereumjs-rlp": "5.0.4", @@ -678,32 +746,11 @@ } } }, - "node_modules/@nomicfoundation/ethereumjs-tx/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, "node_modules/@nomicfoundation/ethereumjs-util": { "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.4.tgz", + "integrity": "sha512-sLOzjnSrlx9Bb9EFNtHzK/FJFsfg2re6bsGqinFinH1gCqVfz9YYlXiMWwDM4C/L4ywuHFCYwfKTVr/QHQcU0Q==", "dev": true, - "license": "MPL-2.0", "dependencies": { "@nomicfoundation/ethereumjs-rlp": "5.0.4", "ethereum-cryptography": "0.1.3" @@ -720,32 +767,11 @@ } } }, - "node_modules/@nomicfoundation/ethereumjs-util/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, "node_modules/@nomicfoundation/hardhat-chai-matchers": { - "version": "2.0.6", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-2.0.8.tgz", + "integrity": "sha512-Z5PiCXH4xhNLASROlSUOADfhfpfhYO6D7Hn9xp8PddmHey0jq704cr6kfU8TRrQ4PUZbpfsZadPj+pCfZdjPIg==", "dev": true, - "license": "MIT", "dependencies": { "@types/chai-as-promised": "^7.1.3", "chai-as-promised": "^7.1.1", @@ -760,9 +786,10 @@ } }, "node_modules/@nomicfoundation/hardhat-ethers": { - "version": "3.0.5", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ethers/-/hardhat-ethers-3.0.8.tgz", + "integrity": "sha512-zhOZ4hdRORls31DTOqg+GmEZM0ujly8GGIuRY7t7szEk2zW/arY1qDug/py8AEktT00v5K+b6RvbVog+va51IA==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^4.1.1", "lodash.isequal": "^4.5.0" @@ -773,11 +800,10 @@ } }, "node_modules/@nomicfoundation/hardhat-network-helpers": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.11.tgz", - "integrity": "sha512-uGPL7QSKvxrHRU69dx8jzoBvuztlLCtyFsbgfXIwIjnO3dqZRz2GNMHJoO3C3dIiUNM6jdNF4AUnoQKDscdYrA==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.12.tgz", + "integrity": "sha512-xTNQNI/9xkHvjmCJnJOTyqDSl8uq1rKb2WOVmixQxFtRd7Oa3ecO8zM0cyC2YmOK+jHB9WPZ+F/ijkHg1CoORA==", "dev": true, - "license": "MIT", "dependencies": { "ethereumjs-util": "^7.1.4" }, @@ -785,158 +811,184 @@ "hardhat": "^2.9.5" } }, - "node_modules/@nomicfoundation/hardhat-network-helpers/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "node_modules/@nomicfoundation/solidity-analyzer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.2.tgz", + "integrity": "sha512-q4n32/FNKIhQ3zQGGw5CvPF6GTvDCpYwIf7bEY/dZTZbgfDsHyjJwURxUJf3VQuuJj+fDIFl4+KkBVbw4Ef6jA==", "dev": true, - "license": "MIT", - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" + "engines": { + "node": ">= 12" + }, + "optionalDependencies": { + "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.1.2", + "@nomicfoundation/solidity-analyzer-darwin-x64": "0.1.2", + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.1.2", + "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.1.2", + "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.1.2", + "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.1.2", + "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.1.2" } }, - "node_modules/@nomicfoundation/hardhat-network-helpers/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", + "node_modules/@nomicfoundation/solidity-analyzer-darwin-arm64": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.2.tgz", + "integrity": "sha512-JaqcWPDZENCvm++lFFGjrDd8mxtf+CtLd2MiXvMNTBD33dContTZ9TWETwNFwg7JTJT5Q9HEecH7FA+HTSsIUw==", "dev": true, - "license": "MPL-2.0", - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, + "optional": true, "engines": { - "node": ">=10.0.0" + "node": ">= 12" } }, - "node_modules/@nomicfoundation/solidity-analyzer": { - "version": "0.1.1", + "node_modules/@nomicfoundation/solidity-analyzer-darwin-x64": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.2.tgz", + "integrity": "sha512-fZNmVztrSXC03e9RONBT+CiksSeYcxI1wlzqyr0L7hsQlK1fzV+f04g2JtQ1c/Fe74ZwdV6aQBdd6Uwl1052sw==", "dev": true, - "license": "MIT", + "optional": true, "engines": { "node": ">= 12" - }, - "optionalDependencies": { - "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.1.1", - "@nomicfoundation/solidity-analyzer-darwin-x64": "0.1.1", - "@nomicfoundation/solidity-analyzer-freebsd-x64": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.1.1", - "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": "0.1.1", - "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": "0.1.1", - "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.1.1" } }, - "node_modules/@nomicfoundation/solidity-analyzer-darwin-arm64": { - "version": "0.1.1", - "cpu": [ - "arm64" - ], + "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.2.tgz", + "integrity": "sha512-3d54oc+9ZVBuB6nbp8wHylk4xh0N0Gc+bk+/uJae+rUgbOBwQSfuGIbAZt1wBXs5REkSmynEGcqx6DutoK0tPA==", "dev": true, - "license": "MIT", "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": ">= 10" + "node": ">= 12" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-musl": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.2.tgz", + "integrity": "sha512-iDJfR2qf55vgsg7BtJa7iPiFAsYf2d0Tv/0B+vhtnI16+wfQeTbP7teookbGvAo0eJo7aLLm0xfS/GTkvHIucA==", + "dev": true, + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-gnu": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.2.tgz", + "integrity": "sha512-9dlHMAt5/2cpWyuJ9fQNOUXFB/vgSFORg1jpjX1Mh9hJ/MfZXlDdHQ+DpFCs32Zk5pxRBb07yGvSHk9/fezL+g==", + "dev": true, + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-musl": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.2.tgz", + "integrity": "sha512-GzzVeeJob3lfrSlDKQw2bRJ8rBf6mEYaWY+gW0JnTDHINA0s2gPR4km5RLIj1xeZZOYz4zRw+AEeYgLRqB2NXg==", + "dev": true, + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-win32-x64-msvc": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.2.tgz", + "integrity": "sha512-Fdjli4DCcFHb4Zgsz0uEJXZ2K7VEO+w5KVv7HmT7WO10iODdU9csC2az4jrhEsRtiR9Gfd74FlG0NYlw1BMdyA==", + "dev": true, + "optional": true, + "engines": { + "node": ">= 12" } }, "node_modules/@openzeppelin/contracts": { "version": "5.0.2", - "license": "MIT" + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-5.0.2.tgz", + "integrity": "sha512-ytPc6eLGcHHnapAZ9S+5qsdomhjo6QBHTDRRBFfTxXIpsicMhVPouPgmUPebZZZGX7vt9USA+Z+0M0dSVtSUEA==" }, "node_modules/@openzeppelin/contracts-upgradeable": { "version": "5.0.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-5.0.2.tgz", + "integrity": "sha512-0MmkHSHiW2NRFiT9/r5Lu4eJq5UJ4/tzlOgYXNAIj/ONkQTVnz22pLxDvp4C4uZ9he7ZFvGn3Driptn1/iU7tQ==", "peerDependencies": { "@openzeppelin/contracts": "5.0.2" } }, "node_modules/@scure/base": { - "version": "1.1.6", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", "dev": true, - "license": "MIT", "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@scure/bip32": { - "version": "1.1.5", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT", "dependencies": { - "@noble/hashes": "~1.2.0", - "@noble/secp256k1": "~1.7.0", - "@scure/base": "~1.1.0" + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "dev": true, + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/@scure/bip32/node_modules/@noble/hashes": { - "version": "1.2.0", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT" + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } }, "node_modules/@scure/bip39": { - "version": "1.1.1", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", + "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT", "dependencies": { - "@noble/hashes": "~1.2.0", - "@scure/base": "~1.1.0" + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/@scure/bip39/node_modules/@noble/hashes": { - "version": "1.2.0", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT" + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } }, "node_modules/@sentry/core": { "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz", + "integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "@sentry/hub": "5.30.0", "@sentry/minimal": "5.30.0", @@ -950,13 +1002,15 @@ }, "node_modules/@sentry/core/node_modules/tslib": { "version": "1.14.1", - "dev": true, - "license": "0BSD" + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true }, "node_modules/@sentry/hub": { "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.30.0.tgz", + "integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "@sentry/types": "5.30.0", "@sentry/utils": "5.30.0", @@ -968,13 +1022,15 @@ }, "node_modules/@sentry/hub/node_modules/tslib": { "version": "1.14.1", - "dev": true, - "license": "0BSD" + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true }, "node_modules/@sentry/minimal": { "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.30.0.tgz", + "integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "@sentry/hub": "5.30.0", "@sentry/types": "5.30.0", @@ -986,13 +1042,15 @@ }, "node_modules/@sentry/minimal/node_modules/tslib": { "version": "1.14.1", - "dev": true, - "license": "0BSD" + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true }, "node_modules/@sentry/node": { "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-5.30.0.tgz", + "integrity": "sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "@sentry/core": "5.30.0", "@sentry/hub": "5.30.0", @@ -1010,13 +1068,15 @@ }, "node_modules/@sentry/node/node_modules/tslib": { "version": "1.14.1", - "dev": true, - "license": "0BSD" + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true }, "node_modules/@sentry/tracing": { "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-5.30.0.tgz", + "integrity": "sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==", "dev": true, - "license": "MIT", "dependencies": { "@sentry/hub": "5.30.0", "@sentry/minimal": "5.30.0", @@ -1030,21 +1090,24 @@ }, "node_modules/@sentry/tracing/node_modules/tslib": { "version": "1.14.1", - "dev": true, - "license": "0BSD" + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true }, "node_modules/@sentry/types": { "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz", + "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=6" } }, "node_modules/@sentry/utils": { "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz", + "integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "@sentry/types": "5.30.0", "tslib": "^1.9.3" @@ -1055,31 +1118,36 @@ }, "node_modules/@sentry/utils/node_modules/tslib": { "version": "1.14.1", - "dev": true, - "license": "0BSD" + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true }, "node_modules/@solidity-parser/parser": { - "version": "0.17.0", - "dev": true, - "license": "MIT" + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz", + "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==", + "dev": true }, "node_modules/@types/bn.js": { - "version": "5.1.5", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.6.tgz", + "integrity": "sha512-Xh8vSwUeMKeYYrj3cX4lGQgFSF/N03r+tv4AiLl1SucqV+uTQpxRcnM8AkXKHwYP9ZPXOYXRr2KPXpVlIvqh9w==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/chai": { - "version": "4.3.16", - "dev": true, - "license": "MIT" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.0.0.tgz", + "integrity": "sha512-+DwhEHAaFPPdJ2ral3kNHFQXnTfscEEFsUxzD+d7nlcLrFK23JtNjH71RGasTcHb88b4vVi4mTyfpf8u2L8bdA==", + "dev": true }, "node_modules/@types/chai-as-promised": { "version": "7.1.8", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.8.tgz", + "integrity": "sha512-ThlRVIJhr69FLlh6IctTXFkmhtP3NpMZ2QGq69StYLyKZFp/HOp1VdKZj7RvfNWYYcJ1xlbLGLLWj1UvP5u/Gw==", "dev": true, - "license": "MIT", "dependencies": { "@types/chai": "*" } @@ -1089,7 +1157,6 @@ "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", "dev": true, - "license": "MIT", "dependencies": { "@types/minimatch": "*", "@types/node": "*" @@ -1097,33 +1164,39 @@ }, "node_modules/@types/lru-cache": { "version": "5.1.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==", + "dev": true }, "node_modules/@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/node": { - "version": "18.15.13", + "version": "22.7.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz", + "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==", "dev": true, - "license": "MIT" + "dependencies": { + "undici-types": "~6.19.2" + } }, "node_modules/@types/pbkdf2": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/secp256k1": { "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.6.tgz", + "integrity": "sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "*" } @@ -1132,26 +1205,28 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/adm-zip": { "version": "0.4.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", + "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.3.0" } }, "node_modules/aes-js": { "version": "4.0.0-beta.5", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", + "dev": true }, "node_modules/agent-base": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, - "license": "MIT", "dependencies": { "debug": "4" }, @@ -1161,8 +1236,9 @@ }, "node_modules/aggregate-error": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, - "license": "MIT", "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -1176,7 +1252,6 @@ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", "dev": true, - "license": "BSD-3-Clause OR MIT", "optional": true, "engines": { "node": ">=0.4.2" @@ -1184,24 +1259,27 @@ }, "node_modules/ansi-align": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", "dev": true, - "license": "ISC", "dependencies": { "string-width": "^4.1.0" } }, "node_modules/ansi-colors": { "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/ansi-escapes": { "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, - "license": "MIT", "dependencies": { "type-fest": "^0.21.3" }, @@ -1214,30 +1292,30 @@ }, "node_modules/ansi-regex": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/ansi-styles": { - "version": "4.3.0", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, - "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "color-convert": "^1.9.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=4" } }, "node_modules/anymatch": { "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, - "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -1248,23 +1326,24 @@ }, "node_modules/argparse": { "version": "2.0.1", - "dev": true, - "license": "Python-2.0" + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/assertion-error": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true, - "license": "MIT", "engines": { "node": "*" } @@ -1273,26 +1352,28 @@ "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/balanced-match": { "version": "1.0.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/base-x": { - "version": "3.0.9", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.10.tgz", + "integrity": "sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ==", "dev": true, - "license": "MIT", "dependencies": { "safe-buffer": "^5.0.1" } }, "node_modules/binary-extensions": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" }, @@ -1302,18 +1383,21 @@ }, "node_modules/blakejs": { "version": "1.2.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", + "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", + "dev": true }, "node_modules/bn.js": { "version": "5.2.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true }, "node_modules/boxen": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", "dev": true, - "license": "MIT", "dependencies": { "ansi-align": "^3.0.0", "camelcase": "^6.2.0", @@ -1331,10 +1415,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/boxen/node_modules/chalk": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1346,18 +1446,38 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/boxen/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/boxen/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/boxen/node_modules/has-flag": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/boxen/node_modules/supports-color": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -1367,8 +1487,9 @@ }, "node_modules/boxen/node_modules/type-fest": { "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -1378,8 +1499,9 @@ }, "node_modules/brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1390,7 +1512,6 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, - "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -1400,18 +1521,21 @@ }, "node_modules/brorand": { "version": "1.1.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true }, "node_modules/browser-stdout": { "version": "1.3.1", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true }, "node_modules/browserify-aes": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, - "license": "MIT", "dependencies": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -1423,16 +1547,18 @@ }, "node_modules/bs58": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", "dev": true, - "license": "MIT", "dependencies": { "base-x": "^3.0.2" } }, "node_modules/bs58check": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", "dev": true, - "license": "MIT", "dependencies": { "bs58": "^4.0.0", "create-hash": "^1.1.0", @@ -1441,26 +1567,30 @@ }, "node_modules/buffer-from": { "version": "1.1.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true }, "node_modules/buffer-xor": { "version": "1.0.3", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true }, "node_modules/bytes": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/camelcase": { "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, - "license": "MIT", "engines": { "node": ">=10" }, @@ -1469,9 +1599,10 @@ } }, "node_modules/chai": { - "version": "4.4.1", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, - "license": "MIT", "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", @@ -1479,27 +1610,29 @@ "get-func-name": "^2.0.2", "loupe": "^2.3.6", "pathval": "^1.1.1", - "type-detect": "^4.0.8" + "type-detect": "^4.1.0" }, "engines": { "node": ">=4" } }, "node_modules/chai-as-promised": { - "version": "7.1.1", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.2.tgz", + "integrity": "sha512-aBDHZxRzYnUYuIAIPBH2s511DjlKPzXNlXSGFC8CwmroWQLfrW0LtE1nK3MAwwNhJPa9raEjNCmRoFpG0Hurdw==", "dev": true, - "license": "WTFPL", "dependencies": { "check-error": "^1.0.2" }, "peerDependencies": { - "chai": ">= 2.1.2 < 5" + "chai": ">= 2.1.2 < 6" } }, "node_modules/chalk": { "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -1509,34 +1642,11 @@ "node": ">=4" } }, - "node_modules/chalk/node_modules/ansi-styles": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/node_modules/color-convert": { - "version": "1.9.3", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/chalk/node_modules/color-name": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, "node_modules/check-error": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", "dev": true, - "license": "MIT", "dependencies": { "get-func-name": "^2.0.2" }, @@ -1545,37 +1655,31 @@ } }, "node_modules/chokidar": { - "version": "3.6.0", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", "dev": true, - "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" } }, "node_modules/ci-info": { "version": "2.0.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true }, "node_modules/cipher-base": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", "dev": true, - "license": "MIT", "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -1583,16 +1687,18 @@ }, "node_modules/clean-stack": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/cli-boxes": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" }, @@ -1602,8 +1708,9 @@ }, "node_modules/cliui": { "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, - "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -1614,48 +1721,55 @@ } }, "node_modules/color-convert": { - "version": "2.0.1", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, - "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "color-name": "1.1.3" } }, "node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, "node_modules/command-exists": { "version": "1.2.9", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "dev": true }, "node_modules/commander": { - "version": "3.0.2", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true, - "license": "MIT" + "engines": { + "node": ">= 12" + } }, "node_modules/concat-map": { "version": "0.0.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/cookie": { "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/create-hash": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, - "license": "MIT", "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -1666,8 +1780,9 @@ }, "node_modules/create-hmac": { "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, - "license": "MIT", "dependencies": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -1684,11 +1799,12 @@ "dev": true }, "node_modules/debug": { - "version": "4.3.4", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, - "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -1701,8 +1817,9 @@ }, "node_modules/decamelize": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=10" }, @@ -1711,9 +1828,10 @@ } }, "node_modules/deep-eql": { - "version": "4.1.3", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "dev": true, - "license": "MIT", "dependencies": { "type-detect": "^4.0.0" }, @@ -1725,21 +1843,22 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/depd": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/diff": { - "version": "5.0.0", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -1761,7 +1880,6 @@ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, - "license": "MIT", "dependencies": { "path-type": "^4.0.0" }, @@ -1771,8 +1889,9 @@ }, "node_modules/elliptic": { "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "dev": true, - "license": "MIT", "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -1785,18 +1904,21 @@ }, "node_modules/elliptic/node_modules/bn.js": { "version": "4.12.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true }, "node_modules/emoji-regex": { "version": "8.0.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "node_modules/enquirer": { "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, - "license": "MIT", "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" @@ -1807,24 +1929,27 @@ }, "node_modules/env-paths": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/escalade": { - "version": "3.1.2", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/escape-string-regexp": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -1834,7 +1959,6 @@ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", "integrity": "sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esprima": "^2.7.1", "estraverse": "^1.9.1", @@ -1852,25 +1976,11 @@ "source-map": "~0.2.0" } }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", - "dev": true, - "optional": true, - "dependencies": { - "amdefine": ">=0.0.4" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/esprima": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", "dev": true, - "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -1893,7 +2003,6 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -1903,7 +2012,6 @@ "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.2.0.tgz", "integrity": "sha512-28hyiE7HVsWubqhpVLVmZXFd4ITeHi+BUu05o9isf0GUpMtzBUi+8/gFrGaGYzvGAJQmJ3JKj77Mk9G98T84rA==", "dev": true, - "license": "MIT", "dependencies": { "@noble/hashes": "^1.4.0" } @@ -1913,7 +2021,6 @@ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", "dev": true, - "license": "MIT", "engines": { "node": "^14.21.3 || >=16" }, @@ -1922,92 +2029,88 @@ } }, "node_modules/ethereum-cryptography": { - "version": "1.2.0", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", "dev": true, - "license": "MIT", "dependencies": { - "@noble/hashes": "1.2.0", - "@noble/secp256k1": "1.7.1", - "@scure/bip32": "1.1.5", - "@scure/bip39": "1.1.1" + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" } }, - "node_modules/ethereum-cryptography/node_modules/@noble/hashes": { - "version": "1.2.0", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT" - }, "node_modules/ethereumjs-abi": { "version": "0.6.8", + "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz", + "integrity": "sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==", "dev": true, - "license": "MIT", "dependencies": { "bn.js": "^4.11.8", "ethereumjs-util": "^6.0.0" } }, + "node_modules/ethereumjs-abi/node_modules/@types/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/ethereumjs-abi/node_modules/bn.js": { "version": "4.12.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true }, - "node_modules/ethereumjs-util": { + "node_modules/ethereumjs-abi/node_modules/ethereumjs-util": { "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", "dev": true, - "license": "MPL-2.0", "dependencies": { "@types/bn.js": "^4.11.3", "bn.js": "^4.11.0", "create-hash": "^1.1.2", "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "0.1.6", - "rlp": "^2.2.3" - } - }, - "node_modules/ethereumjs-util/node_modules/@types/bn.js": { - "version": "4.11.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" } }, - "node_modules/ethereumjs-util/node_modules/bn.js": { - "version": "4.12.0", - "dev": true, - "license": "MIT" - }, - "node_modules/ethereumjs-util/node_modules/ethereum-cryptography": { - "version": "0.1.3", + "node_modules/ethereumjs-util": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", + "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", "dev": true, - "license": "MIT", "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" + "@types/bn.js": "^5.1.0", + "bn.js": "^5.1.2", + "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", + "rlp": "^2.2.4" + }, + "engines": { + "node": ">=10.0.0" } }, "node_modules/ethers": { - "version": "6.12.1", + "version": "6.13.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.2.tgz", + "integrity": "sha512-9VkriTTed+/27BGuY1s0hf441kqwHJ1wtN2edksEtiRvXx+soxRX3iSXTfFqq2+YwrOqbDoTHjIhQnjJRlzKmg==", "dev": true, "funding": [ { @@ -2019,7 +2122,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "license": "MIT", "dependencies": { "@adraffy/ens-normalize": "1.10.1", "@noble/curves": "1.2.0", @@ -2027,18 +2129,23 @@ "@types/node": "18.15.13", "aes-js": "4.0.0-beta.5", "tslib": "2.4.0", - "ws": "8.5.0" + "ws": "8.17.1" }, "engines": { "node": ">=14.0.0" } }, + "node_modules/ethers/node_modules/@types/node": { + "version": "18.15.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", + "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==", + "dev": true + }, "node_modules/ethjs-unit": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", "integrity": "sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==", "dev": true, - "license": "MIT", "dependencies": { "bn.js": "4.11.6", "number-to-bn": "1.7.0" @@ -2052,13 +2159,13 @@ "version": "4.11.6", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/ethjs-util": { "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", + "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", "dev": true, - "license": "MIT", "dependencies": { "is-hex-prefixed": "1.0.0", "strip-hex-prefix": "1.0.0" @@ -2070,8 +2177,9 @@ }, "node_modules/evp_bytestokey": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "dev": true, - "license": "MIT", "dependencies": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" @@ -2082,7 +2190,6 @@ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -2098,15 +2205,13 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, - "license": "ISC", "dependencies": { "reusify": "^1.0.4" } @@ -2116,7 +2221,6 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, - "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2126,8 +2230,9 @@ }, "node_modules/find-up": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", "dev": true, - "license": "MIT", "dependencies": { "locate-path": "^2.0.0" }, @@ -2137,14 +2242,17 @@ }, "node_modules/flat": { "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, - "license": "BSD-3-Clause", "bin": { "flat": "cli.js" } }, "node_modules/follow-redirects": { - "version": "1.15.6", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "dev": true, "funding": [ { @@ -2152,7 +2260,6 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], - "license": "MIT", "engines": { "node": ">=4.0" }, @@ -2164,13 +2271,15 @@ }, "node_modules/fp-ts": { "version": "1.19.3", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-1.19.3.tgz", + "integrity": "sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==", + "dev": true }, "node_modules/fs-extra": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, - "license": "MIT", "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -2182,13 +2291,16 @@ }, "node_modules/fs.realpath": { "version": "1.0.0", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "license": "MIT", + "hasInstallScript": true, "optional": true, "os": [ "darwin" @@ -2199,16 +2311,18 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, - "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-func-name": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, - "license": "MIT", "engines": { "node": "*" } @@ -2218,7 +2332,6 @@ "resolved": "https://registry.npmjs.org/ghost-testrpc/-/ghost-testrpc-0.0.2.tgz", "integrity": "sha512-i08dAEgJ2g8z5buJIrCTduwPIhih3DP+hOCTyyryikfV8T0bNvHnGXO67i0DD1H4GBDETTclPy9njZbfluQYrQ==", "dev": true, - "license": "ISC", "dependencies": { "chalk": "^2.4.2", "node-emoji": "^1.10.0" @@ -2229,8 +2342,10 @@ }, "node_modules/glob": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2248,8 +2363,9 @@ }, "node_modules/glob-parent": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -2262,7 +2378,6 @@ "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", "dev": true, - "license": "MIT", "dependencies": { "global-prefix": "^3.0.0" }, @@ -2275,7 +2390,6 @@ "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", "dev": true, - "license": "MIT", "dependencies": { "ini": "^1.3.5", "kind-of": "^6.0.2", @@ -2290,7 +2404,6 @@ "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", "dev": true, - "license": "MIT", "dependencies": { "@types/glob": "^7.1.1", "array-union": "^2.1.0", @@ -2307,15 +2420,15 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true }, "node_modules/handlebars": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, - "license": "MIT", "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", @@ -2332,14 +2445,24 @@ "uglify-js": "^3.1.4" } }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/hardhat": { - "version": "2.22.3", + "version": "2.22.12", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.22.12.tgz", + "integrity": "sha512-yok65M+LsOeTBHQsjg//QreGCyrsaNmeLVzhTFqlOvZ4ZE5y69N0wRxH1b2BC9dGK8S8OPUJMNiL9X0RAvbm8w==", "dev": true, - "license": "MIT", "dependencies": { "@ethersproject/abi": "^5.1.2", "@metamask/eth-sig-util": "^4.0.0", - "@nomicfoundation/edr": "^0.3.5", + "@nomicfoundation/edr": "^0.6.1", "@nomicfoundation/ethereumjs-common": "4.0.4", "@nomicfoundation/ethereumjs-tx": "5.0.4", "@nomicfoundation/ethereumjs-util": "9.0.4", @@ -2352,7 +2475,7 @@ "ansi-escapes": "^4.3.0", "boxen": "^5.1.2", "chalk": "^2.4.2", - "chokidar": "^3.4.0", + "chokidar": "^4.0.0", "ci-info": "^2.0.0", "debug": "^4.1.1", "enquirer": "^2.3.0", @@ -2365,6 +2488,7 @@ "glob": "7.2.0", "immutable": "^4.0.0-rc.12", "io-ts": "1.10.4", + "json-stream-stringify": "^3.1.4", "keccak": "^3.0.2", "lodash": "^4.17.11", "mnemonist": "^0.38.0", @@ -2373,7 +2497,7 @@ "raw-body": "^2.4.1", "resolve": "1.17.0", "semver": "^6.3.0", - "solc": "0.7.3", + "solc": "0.8.26", "source-map-support": "^0.5.13", "stacktrace-parser": "^0.1.10", "tsort": "0.0.1", @@ -2402,7 +2526,6 @@ "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.15.tgz", "integrity": "sha512-jqxErCnSWGYf4vAkLmh3H3u+IuLuCLw/EVeV13z1JKJMJAd/iO+G283n8T124S/Q2BF/BoA2zgzYAlqXgNyKew==", "dev": true, - "license": "MIT", "dependencies": { "micromatch": "^4.0.4", "solidity-ast": "^0.4.52" @@ -2411,10 +2534,68 @@ "hardhat": "^2.3.0" } }, + "node_modules/hardhat/node_modules/@noble/hashes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", + "integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/hardhat/node_modules/@scure/bip32": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz", + "integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "@noble/hashes": "~1.2.0", + "@noble/secp256k1": "~1.7.0", + "@scure/base": "~1.1.0" + } + }, + "node_modules/hardhat/node_modules/@scure/bip39": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz", + "integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "@noble/hashes": "~1.2.0", + "@scure/base": "~1.1.0" + } + }, + "node_modules/hardhat/node_modules/ethereum-cryptography": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", + "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", + "dev": true, + "dependencies": { + "@noble/hashes": "1.2.0", + "@noble/secp256k1": "1.7.1", + "@scure/bip32": "1.1.5", + "@scure/bip39": "1.1.1" + } + }, "node_modules/hardhat/node_modules/ws": { - "version": "7.5.9", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8.3.0" }, @@ -2433,16 +2614,18 @@ }, "node_modules/has-flag": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/hash-base": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", "dev": true, - "license": "MIT", "dependencies": { "inherits": "^2.0.4", "readable-stream": "^3.6.0", @@ -2454,8 +2637,9 @@ }, "node_modules/hash.js": { "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "dev": true, - "license": "MIT", "dependencies": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" @@ -2463,8 +2647,9 @@ }, "node_modules/he": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, - "license": "MIT", "bin": { "he": "bin/he" } @@ -2473,13 +2658,13 @@ "version": "0.2.7", "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/hmac-drbg": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", "dev": true, - "license": "MIT", "dependencies": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -2488,8 +2673,9 @@ }, "node_modules/http-errors": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, - "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -2503,8 +2689,9 @@ }, "node_modules/https-proxy-agent": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, - "license": "MIT", "dependencies": { "agent-base": "6", "debug": "4" @@ -2515,8 +2702,9 @@ }, "node_modules/iconv-lite": { "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, - "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -2529,28 +2717,31 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/immutable": { - "version": "4.3.5", - "dev": true, - "license": "MIT" + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "dev": true }, "node_modules/indent-string": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/inflight": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, - "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -2558,38 +2749,39 @@ }, "node_modules/inherits": { "version": "2.0.4", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/interpret": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.10" } }, "node_modules/io-ts": { "version": "1.10.4", + "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-1.10.4.tgz", + "integrity": "sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==", "dev": true, - "license": "MIT", "dependencies": { "fp-ts": "^1.0.0" } }, "node_modules/is-binary-path": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, - "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -2599,24 +2791,27 @@ }, "node_modules/is-extglob": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/is-glob": { "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -2626,8 +2821,9 @@ }, "node_modules/is-hex-prefixed": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", + "integrity": "sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.5.0", "npm": ">=3" @@ -2638,23 +2834,24 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.12.0" } }, "node_modules/is-plain-obj": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/is-unicode-supported": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, - "license": "MIT", "engines": { "node": ">=10" }, @@ -2666,18 +2863,19 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/js-sha3": { "version": "0.8.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "dev": true }, "node_modules/js-yaml": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -2685,10 +2883,20 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-stream-stringify": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/json-stream-stringify/-/json-stream-stringify-3.1.5.tgz", + "integrity": "sha512-wurRuTiw27mck9MWaUIGAunfwqhPDxnXQVN/+Rzi+IEQUUALU10AZs1nWkSdtjH7PAVuAUcqQjH11S/JHOWeaA==", + "dev": true, + "engines": { + "node": ">=7.10.1" + } + }, "node_modules/jsonfile": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, - "license": "MIT", "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -2698,16 +2906,16 @@ "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", "dev": true, - "license": "MIT", "engines": { "node": "*" } }, "node_modules/keccak": { "version": "3.0.4", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", + "integrity": "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==", "dev": true, "hasInstallScript": true, - "license": "MIT", "dependencies": { "node-addon-api": "^2.0.0", "node-gyp-build": "^4.2.0", @@ -2722,25 +2930,15 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/klaw": { - "version": "1.3.1", - "dev": true, - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.9" - } - }, "node_modules/levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", "dev": true, - "license": "MIT", "dependencies": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" @@ -2751,8 +2949,9 @@ }, "node_modules/locate-path": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", "dev": true, - "license": "MIT", "dependencies": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" @@ -2763,18 +2962,21 @@ }, "node_modules/lodash": { "version": "4.17.21", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true }, "node_modules/lodash.isequal": { "version": "4.5.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "dev": true }, "node_modules/log-symbols": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, - "license": "MIT", "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -2786,10 +2988,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/log-symbols/node_modules/chalk": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2801,18 +3019,38 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/log-symbols/node_modules/has-flag": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/log-symbols/node_modules/supports-color": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -2822,21 +3060,24 @@ }, "node_modules/loupe": { "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", "dev": true, - "license": "MIT", "dependencies": { "get-func-name": "^2.0.1" } }, "node_modules/lru_map": { "version": "0.3.3", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==", + "dev": true }, "node_modules/md5.js": { "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "dev": true, - "license": "MIT", "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1", @@ -2845,6 +3086,8 @@ }, "node_modules/memorystream": { "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", "dev": true, "engines": { "node": ">= 0.10.0" @@ -2855,7 +3098,6 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 8" } @@ -2864,15 +3106,13 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/micro-ftch/-/micro-ftch-0.3.1.tgz", "integrity": "sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, - "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -2883,18 +3123,21 @@ }, "node_modules/minimalistic-assert": { "version": "1.0.1", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true }, "node_modules/minimalistic-crypto-utils": { "version": "1.0.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true }, "node_modules/minimatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2907,7 +3150,6 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, - "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2917,7 +3159,6 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, - "license": "MIT", "dependencies": { "minimist": "^1.2.6" }, @@ -2927,37 +3168,39 @@ }, "node_modules/mnemonist": { "version": "0.38.5", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.5.tgz", + "integrity": "sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==", "dev": true, - "license": "MIT", "dependencies": { "obliterator": "^2.0.0" } }, "node_modules/mocha": { - "version": "10.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "8.1.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", + "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" }, "bin": { "_mocha": "bin/_mocha", @@ -2967,32 +3210,20 @@ "node": ">= 14.0.0" } }, - "node_modules/mocha/node_modules/ansi-colors": { - "version": "4.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/mocha/node_modules/brace-expansion": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/mocha/node_modules/chokidar": { - "version": "3.5.3", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -3005,14 +3236,18 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "node_modules/mocha/node_modules/cliui": { "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, - "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -3021,8 +3256,9 @@ }, "node_modules/mocha/node_modules/escape-string-regexp": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "license": "MIT", "engines": { "node": ">=10" }, @@ -3032,8 +3268,9 @@ }, "node_modules/mocha/node_modules/find-up": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, - "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -3047,8 +3284,10 @@ }, "node_modules/mocha/node_modules/glob": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3065,16 +3304,18 @@ }, "node_modules/mocha/node_modules/has-flag": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/mocha/node_modules/locate-path": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -3086,9 +3327,10 @@ } }, "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -3096,15 +3338,11 @@ "node": ">=10" } }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, "node_modules/mocha/node_modules/p-limit": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -3117,8 +3355,9 @@ }, "node_modules/mocha/node_modules/p-locate": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -3131,16 +3370,30 @@ }, "node_modules/mocha/node_modules/path-exists": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/mocha/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/mocha/node_modules/supports-color": { "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -3153,8 +3406,9 @@ }, "node_modules/mocha/node_modules/yargs": { "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, - "license": "MIT", "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -3168,45 +3422,38 @@ "node": ">=10" } }, - "node_modules/mocha/node_modules/yargs-parser": { - "version": "20.2.4", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/ms": { - "version": "2.1.2", - "dev": true, - "license": "MIT" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/node-addon-api": { "version": "2.0.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", + "dev": true }, "node_modules/node-emoji": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", "dev": true, - "license": "MIT", "dependencies": { "lodash": "^4.17.21" } }, "node_modules/node-gyp-build": { - "version": "4.8.1", + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.2.tgz", + "integrity": "sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==", "dev": true, - "license": "MIT", "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -3218,7 +3465,6 @@ "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", "dev": true, - "license": "ISC", "dependencies": { "abbrev": "1" }, @@ -3228,8 +3474,9 @@ }, "node_modules/normalize-path": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -3239,7 +3486,6 @@ "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", "integrity": "sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==", "dev": true, - "license": "MIT", "dependencies": { "bn.js": "4.11.6", "strip-hex-prefix": "1.0.0" @@ -3253,18 +3499,19 @@ "version": "4.11.6", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/obliterator": { "version": "2.0.4", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", + "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==", + "dev": true }, "node_modules/once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, - "license": "ISC", "dependencies": { "wrappy": "1" } @@ -3274,7 +3521,6 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", "dev": true, - "license": "MIT", "dependencies": { "deep-is": "~0.1.3", "fast-levenshtein": "~2.0.6", @@ -3289,21 +3535,24 @@ }, "node_modules/ordinal": { "version": "1.0.3", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/ordinal/-/ordinal-1.0.3.tgz", + "integrity": "sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==", + "dev": true }, "node_modules/os-tmpdir": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/p-limit": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, - "license": "MIT", "dependencies": { "p-try": "^1.0.0" }, @@ -3313,8 +3562,9 @@ }, "node_modules/p-locate": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", "dev": true, - "license": "MIT", "dependencies": { "p-limit": "^1.1.0" }, @@ -3324,8 +3574,9 @@ }, "node_modules/p-map": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, - "license": "MIT", "dependencies": { "aggregate-error": "^3.0.0" }, @@ -3338,55 +3589,60 @@ }, "node_modules/p-try": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/path-exists": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/path-is-absolute": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/path-parse": { "version": "1.0.7", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/pathval": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true, - "license": "MIT", "engines": { "node": "*" } }, "node_modules/pbkdf2": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", "dev": true, - "license": "MIT", "dependencies": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", @@ -3400,8 +3656,9 @@ }, "node_modules/picomatch": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, - "license": "MIT", "engines": { "node": ">=8.6" }, @@ -3414,7 +3671,6 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -3429,9 +3685,10 @@ } }, "node_modules/prettier": { - "version": "3.2.5", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, - "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -3443,13 +3700,13 @@ } }, "node_modules/prettier-plugin-solidity": { - "version": "1.3.1", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.4.1.tgz", + "integrity": "sha512-Mq8EtfacVZ/0+uDKTtHZGW3Aa7vEbX/BNx63hmVg6YTiTXSiuKP0amj0G6pGwjmLaOfymWh3QgXEZkjQbU8QRg==", "dev": true, - "license": "MIT", "dependencies": { - "@solidity-parser/parser": "^0.17.0", - "semver": "^7.5.4", - "solidity-comments-extractor": "^0.0.8" + "@solidity-parser/parser": "^0.18.0", + "semver": "^7.5.4" }, "engines": { "node": ">=16" @@ -3459,9 +3716,10 @@ } }, "node_modules/prettier-plugin-solidity/node_modules/semver": { - "version": "7.6.2", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -3487,21 +3745,22 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" + ] }, "node_modules/randombytes": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, - "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } }, "node_modules/raw-body": { "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, - "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -3514,8 +3773,9 @@ }, "node_modules/readable-stream": { "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, - "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -3526,14 +3786,16 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.1.tgz", + "integrity": "sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==", "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, "engines": { - "node": ">=8.10.0" + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/rechoir": { @@ -3553,7 +3815,6 @@ "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", "dev": true, - "license": "MIT", "dependencies": { "minimatch": "^3.0.5" }, @@ -3563,24 +3824,18 @@ }, "node_modules/require-directory": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/resolve": { "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "dev": true, - "license": "MIT", "dependencies": { "path-parse": "^1.0.6" }, @@ -3593,27 +3848,16 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true, - "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "2.7.1", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/ripemd160": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", "dev": true, - "license": "MIT", "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1" @@ -3621,8 +3865,9 @@ }, "node_modules/rlp": { "version": "2.2.7", + "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", + "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", "dev": true, - "license": "MPL-2.0", "dependencies": { "bn.js": "^5.2.0" }, @@ -3649,13 +3894,14 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } }, "node_modules/safe-buffer": { "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, "funding": [ { @@ -3670,20 +3916,19 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" + ] }, "node_modules/safer-buffer": { "version": "2.1.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "node_modules/sc-istanbul": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/sc-istanbul/-/sc-istanbul-0.4.6.tgz", "integrity": "sha512-qJFF/8tW/zJsbyfh/iT/ZM5QNHE3CXxtLJbZsL+CzdJLBsPD7SedJZoUA4d8iAcN2IoMp/Dx80shOOd2x96X/g==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "abbrev": "1.0.x", "async": "1.x", @@ -3709,7 +3954,6 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } @@ -3720,7 +3964,6 @@ "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "license": "ISC", "dependencies": { "inflight": "^1.0.4", "inherits": "2", @@ -3737,7 +3980,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -3747,7 +3989,6 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, - "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -3761,7 +4002,6 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, - "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -3774,15 +4014,13 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/sc-istanbul/node_modules/supports-color": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^1.0.0" }, @@ -3792,14 +4030,16 @@ }, "node_modules/scrypt-js": { "version": "3.0.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", + "dev": true }, "node_modules/secp256k1": { "version": "4.0.3", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", + "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", "dev": true, "hasInstallScript": true, - "license": "MIT", "dependencies": { "elliptic": "^6.5.4", "node-addon-api": "^2.0.0", @@ -3811,34 +4051,39 @@ }, "node_modules/semver": { "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/serialize-javascript": { - "version": "6.0.0", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } }, "node_modules/setimmediate": { "version": "1.0.5", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true }, "node_modules/setprototypeof": { "version": "1.2.0", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true }, "node_modules/sha.js": { "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, - "license": "(MIT AND BSD-3-Clause)", "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -3852,7 +4097,6 @@ "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "glob": "^7.0.0", "interpret": "^1.0.0", @@ -3870,79 +4114,51 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/solc": { - "version": "0.7.3", + "version": "0.8.26", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.26.tgz", + "integrity": "sha512-yiPQNVf5rBFHwN6SIf3TUUvVAFKcQqmSUFeq+fb6pNRCo0ZCgpYOZDi3BVoezCPIAcKrVYd/qXlBLUP9wVrZ9g==", "dev": true, - "license": "MIT", "dependencies": { "command-exists": "^1.2.8", - "commander": "3.0.2", + "commander": "^8.1.0", "follow-redirects": "^1.12.1", - "fs-extra": "^0.30.0", "js-sha3": "0.8.0", "memorystream": "^0.3.1", - "require-from-string": "^2.0.0", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { - "solcjs": "solcjs" + "solcjs": "solc.js" }, "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/solc/node_modules/fs-extra": { - "version": "0.30.0", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0", - "path-is-absolute": "^1.0.0", - "rimraf": "^2.2.8" - } - }, - "node_modules/solc/node_modules/jsonfile": { - "version": "2.4.0", - "dev": true, - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "node": ">=10.0.0" } }, "node_modules/solc/node_modules/semver": { "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver" } }, "node_modules/solidity-ast": { - "version": "0.4.58", - "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.58.tgz", - "integrity": "sha512-fiAEDlMEc+xziMn0IpZf2vUbqxyXYZK4BqBiTaz2ZUqOP0p1fdJzUc9xpv74Jdxb5BLAiCUFv5UenkXIpHn3cA==", - "dev": true, - "license": "MIT" - }, - "node_modules/solidity-comments-extractor": { - "version": "0.0.8", - "dev": true, - "license": "MIT" + "version": "0.4.59", + "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.59.tgz", + "integrity": "sha512-I+CX0wrYUN9jDfYtcgWSe+OAowaXy8/1YQy7NS4ni5IBDmIYBq7ZzaP/7QqouLjzZapmQtvGLqCaYgoUWqBo5g==", + "dev": true }, "node_modules/solidity-coverage": { "version": "0.8.13", "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.13.tgz", "integrity": "sha512-RiBoI+kF94V3Rv0+iwOj3HQVSqNzA9qm/qDP1ZDXK5IX0Cvho1qiz8hAXTsAo6KOIUeP73jfscq0KlLqVxzGWA==", "dev": true, - "license": "ISC", "dependencies": { "@ethersproject/abi": "^5.0.9", "@solidity-parser/parser": "^0.18.0", @@ -3971,19 +4187,11 @@ "hardhat": "^2.11.0" } }, - "node_modules/solidity-coverage/node_modules/@solidity-parser/parser": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz", - "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==", - "dev": true, - "license": "MIT" - }, "node_modules/solidity-coverage/node_modules/fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, - "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", @@ -3998,7 +4206,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -4007,33 +4214,48 @@ } }, "node_modules/source-map": { - "version": "0.6.1", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", "dev": true, - "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, "engines": { - "node": ">=0.10.0" + "node": ">=0.8.0" } }, "node_modules/source-map-support": { "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, - "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" + "dev": true }, "node_modules/stacktrace-parser": { "version": "0.1.10", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", + "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", "dev": true, - "license": "MIT", "dependencies": { "type-fest": "^0.7.1" }, @@ -4043,32 +4265,36 @@ }, "node_modules/stacktrace-parser/node_modules/type-fest": { "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=8" } }, "node_modules/statuses": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/string_decoder": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, - "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } }, "node_modules/string-width": { "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -4080,8 +4306,9 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -4091,8 +4318,9 @@ }, "node_modules/strip-hex-prefix": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", + "integrity": "sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==", "dev": true, - "license": "MIT", "dependencies": { "is-hex-prefixed": "1.0.0" }, @@ -4103,8 +4331,9 @@ }, "node_modules/strip-json-comments": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" }, @@ -4114,8 +4343,9 @@ }, "node_modules/supports-color": { "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -4125,8 +4355,9 @@ }, "node_modules/tmp": { "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, - "license": "MIT", "dependencies": { "os-tmpdir": "~1.0.2" }, @@ -4139,7 +4370,6 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, - "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -4149,38 +4379,42 @@ }, "node_modules/toidentifier": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.6" } }, "node_modules/tslib": { "version": "2.4.0", - "dev": true, - "license": "0BSD" + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true }, "node_modules/tsort": { "version": "0.0.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/tsort/-/tsort-0.0.1.tgz", + "integrity": "sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==", + "dev": true }, "node_modules/tweetnacl": { "version": "1.0.3", - "dev": true, - "license": "Unlicense" + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "dev": true }, "node_modules/tweetnacl-util": { "version": "0.15.1", - "dev": true, - "license": "Unlicense" + "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", + "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", + "dev": true }, "node_modules/type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", "dev": true, - "license": "MIT", "dependencies": { "prelude-ls": "~1.1.2" }, @@ -4189,17 +4423,19 @@ } }, "node_modules/type-detect": { - "version": "4.0.8", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/type-fest": { "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -4212,7 +4448,6 @@ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, - "license": "BSD-2-Clause", "optional": true, "bin": { "uglifyjs": "bin/uglifyjs" @@ -4223,8 +4458,9 @@ }, "node_modules/undici": { "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", "dev": true, - "license": "MIT", "dependencies": { "@fastify/busboy": "^2.0.0" }, @@ -4232,18 +4468,26 @@ "node": ">=14.0" } }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, "node_modules/universalify": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4.0.0" } }, "node_modules/unpipe": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -4252,18 +4496,19 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/util-deprecate": { "version": "1.0.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true }, "node_modules/uuid": { "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true, - "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } @@ -4273,7 +4518,6 @@ "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.4.tgz", "integrity": "sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A==", "dev": true, - "license": "LGPL-3.0", "dependencies": { "@ethereumjs/util": "^8.1.0", "bn.js": "^5.2.1", @@ -4293,7 +4537,6 @@ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", "dev": true, - "license": "MIT", "dependencies": { "@noble/hashes": "1.4.0" }, @@ -4306,7 +4549,6 @@ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 16" }, @@ -4314,41 +4556,11 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/web3-utils/node_modules/@scure/bip32": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", - "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/curves": "~1.4.0", - "@noble/hashes": "~1.4.0", - "@scure/base": "~1.1.6" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/web3-utils/node_modules/@scure/bip39": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", - "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/hashes": "~1.4.0", - "@scure/base": "~1.1.6" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/web3-utils/node_modules/ethereum-cryptography": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", "dev": true, - "license": "MIT", "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", @@ -4361,7 +4573,6 @@ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, - "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -4371,8 +4582,9 @@ }, "node_modules/widest-line": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", "dev": true, - "license": "MIT", "dependencies": { "string-width": "^4.0.0" }, @@ -4385,7 +4597,6 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -4394,18 +4605,19 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/workerpool": { - "version": "6.2.1", - "dev": true, - "license": "Apache-2.0" + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true }, "node_modules/wrap-ansi": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -4418,21 +4630,56 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/wrappy": { "version": "1.0.2", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true }, "node_modules/ws": { - "version": "8.5.0", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -4445,16 +4692,18 @@ }, "node_modules/y18n": { "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, - "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yargs": { "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, - "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -4469,17 +4718,19 @@ } }, "node_modules/yargs-parser": { - "version": "21.1.1", + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, - "license": "ISC", "engines": { - "node": ">=12" + "node": ">=10" } }, "node_modules/yargs-unparser": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, - "license": "MIT", "dependencies": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", @@ -4490,10 +4741,20 @@ "node": ">=10" } }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=10" }, From 46db15db621e880843f8be80553b57b1d6ad3fba Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 30 Sep 2024 16:17:20 +0200 Subject: [PATCH 36/51] rename --- contracts/crosschain/axelar/AxelarGatewaySource.sol | 2 +- contracts/utils/CAIP-10.sol | 4 ++-- contracts/utils/CAIP-2.sol | 2 +- test/crosschain/utils/CAIP.test.js | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/crosschain/axelar/AxelarGatewaySource.sol b/contracts/crosschain/axelar/AxelarGatewaySource.sol index 7bcc0eda..7a722bdc 100644 --- a/contracts/crosschain/axelar/AxelarGatewaySource.sol +++ b/contracts/crosschain/axelar/AxelarGatewaySource.sol @@ -35,7 +35,7 @@ abstract contract AxelarGatewaySource is IGatewaySource, AxelarGatewayBase { // Emit event emit MessageCreated( 0, - CAIP10.format(CAIP2.format(), sender), + CAIP10.format(CAIP2.local(), sender), CAIP10.format(destination, receiver), payload, attributes diff --git a/contracts/utils/CAIP-10.sol b/contracts/utils/CAIP-10.sol index 4c5a30fc..70ced580 100644 --- a/contracts/utils/CAIP-10.sol +++ b/contracts/utils/CAIP-10.sol @@ -15,8 +15,8 @@ library CAIP10 { using Strings for address; using Bytes for bytes; - function format(address account) internal view returns (string memory) { - return format(CAIP2.format(), account.toHexString()); + function local(address account) internal view returns (string memory) { + return format(CAIP2.local(), account.toHexString()); } function format(string memory caip2, string memory account) internal pure returns (string memory) { diff --git a/contracts/utils/CAIP-2.sol b/contracts/utils/CAIP-2.sol index 9a7c3314..9c79d48c 100644 --- a/contracts/utils/CAIP-2.sol +++ b/contracts/utils/CAIP-2.sol @@ -14,7 +14,7 @@ library CAIP2 { using Strings for uint256; using Bytes for bytes; - function format() internal view returns (string memory) { + function local() internal view returns (string memory) { return format("eip155", block.chainid.toString()); } diff --git a/test/crosschain/utils/CAIP.test.js b/test/crosschain/utils/CAIP.test.js index 58d0c49a..c649354f 100644 --- a/test/crosschain/utils/CAIP.test.js +++ b/test/crosschain/utils/CAIP.test.js @@ -17,8 +17,8 @@ describe('CAIP utilities', function () { }); describe('CAIP-2', function () { - it('format()', async function () { - expect(await this.caip2.$format()).to.equal(format('eip155', this.chainId)); + it('local()', async function () { + expect(await this.caip2.$local()).to.equal(format('eip155', this.chainId)); }); for (const { namespace, reference, caip2 } of Object.values(CHAINS)) @@ -35,9 +35,9 @@ describe('CAIP utilities', function () { describe('CAIP-10', function () { const { address: account } = ethers.Wallet.createRandom(); - it(`format(${account})`, async function () { + it(`local(${account})`, async function () { // lowercase encoding for now - expect(await this.caip10.$format(ethers.Typed.address(account))).to.equal(format('eip155', this.chainId, account.toLowerCase())); + expect(await this.caip10.$local(ethers.Typed.address(account))).to.equal(format('eip155', this.chainId, account.toLowerCase())); }); for (const { account, caip2, caip10 } of Object.values(CHAINS)) From 70855f7088087e8ee8d9a5fb55dbf518e354d104 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 30 Sep 2024 16:19:47 +0200 Subject: [PATCH 37/51] fix foundry --- foundry.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/foundry.toml b/foundry.toml index 24eebb08..cf3fd677 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,5 +1,5 @@ [profile.default] -solc_version = '0.8.26' +solc_version = '0.8.27' evm_version = 'cancun' src = 'contracts' out = 'out' From 3f3d6b61a1ecd3afc0c5e43351940e095765178f Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 30 Sep 2024 16:20:59 +0200 Subject: [PATCH 38/51] codespell --- contracts/utils/Bytes.sol | 4 ++-- contracts/utils/Strings.sol | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/utils/Bytes.sol b/contracts/utils/Bytes.sol index 6cea6e84..2f7e09a0 100644 --- a/contracts/utils/Bytes.sol +++ b/contracts/utils/Bytes.sol @@ -68,7 +68,7 @@ library Bytes { } /** - * @dev Copies the content of `buffer`, for `start` (included) to the end of `buffer` into a new bytes object in + * @dev Copies the content of `buffer`, from `start` (included) to the end of `buffer` into a new bytes object in * memory. * * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice @@ -78,7 +78,7 @@ library Bytes { } /** - * @dev Copies the content of `buffer`, for `start` (included) to the `end` (exluded) into a new bytes object in + * @dev Copies the content of `buffer`, from `start` (included) to `end` (excluded) into a new bytes object in * memory. * * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index 6ef483be..a6f9fc62 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -362,7 +362,7 @@ library StringsUnreleased { uint256 expectedLength = hasPrefix ? 42 : 40; if (end - begin == expectedLength) { - // length garantees that this does not overflow, and value2 is at most type(uint160).max + // length guarantees that this does not overflow, and value2 is at most type(uint160).max (bool s, uint256 v) = tryParseHex(input, begin, end); return (s, address(uint160(v))); } else { From 336a56b5028c40809c38395a72f906f6333f4b41 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 1 Oct 2024 17:01:28 +0200 Subject: [PATCH 39/51] use checksumed addresses --- .../axelar/AxelarGatewayDestination.sol | 9 ++++----- .../crosschain/axelar/AxelarGatewaySource.sol | 6 +++--- .../crosschain/mocks/AxelarGatewayMock.sol | 18 ++++++++++++------ contracts/utils/CAIP-10.sol | 6 +++--- test/crosschain/axelar/AxelarGateway.test.js | 2 +- test/crosschain/utils/CAIP.test.js | 2 +- 6 files changed, 24 insertions(+), 19 deletions(-) diff --git a/contracts/crosschain/axelar/AxelarGatewayDestination.sol b/contracts/crosschain/axelar/AxelarGatewayDestination.sol index e8d3c1ae..27a68ae6 100644 --- a/contracts/crosschain/axelar/AxelarGatewayDestination.sol +++ b/contracts/crosschain/axelar/AxelarGatewayDestination.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.27; import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol"; -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {StringsUnreleased} from "../../utils/Strings.sol"; import {CAIP2} from "../../utils/CAIP-2.sol"; import {CAIP10} from "../../utils/CAIP-10.sol"; @@ -12,8 +11,8 @@ import {IGatewayDestinationPassive} from "../interfaces/IGatewayDestinationPassi import {IGatewayReceiver} from "../interfaces/IGatewayReceiver.sol"; abstract contract AxelarGatewayDestination is IGatewayDestinationPassive, AxelarGatewayBase, AxelarExecutable { - using Strings for address; - using Strings for string; + using StringsUnreleased for address; + using StringsUnreleased for string; /// @dev Passive mode function validateReceivedMessage( @@ -29,7 +28,7 @@ abstract contract AxelarGatewayDestination is IGatewayDestinationPassive, Axelar // Rebuild expected package bytes memory adapterPayload = abi.encode( sender, - msg.sender.toHexString(), // receiver + msg.sender.toChecksumHexString(), // receiver payload, attributes ); @@ -71,7 +70,7 @@ abstract contract AxelarGatewayDestination is IGatewayDestinationPassive, Axelar require(getRemoteGateway(source).equal(remoteAccount), "Invalid origin gateway"); // Active mode - IGatewayReceiver(StringsUnreleased.parseAddress(receiver)).receiveMessage( + IGatewayReceiver(receiver.parseAddress()).receiveMessage( address(0), // not needed in active mode new bytes(0), // not needed in active mode source, diff --git a/contracts/crosschain/axelar/AxelarGatewaySource.sol b/contracts/crosschain/axelar/AxelarGatewaySource.sol index 7a722bdc..a4cfc125 100644 --- a/contracts/crosschain/axelar/AxelarGatewaySource.sol +++ b/contracts/crosschain/axelar/AxelarGatewaySource.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.27; import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; import {IGatewaySource} from "../interfaces/IGatewaySource.sol"; -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {StringsUnreleased} from "../../utils/Strings.sol"; import {CAIP2} from "../../utils/CAIP-2.sol"; import {CAIP10} from "../../utils/CAIP-10.sol"; abstract contract AxelarGatewaySource is IGatewaySource, AxelarGatewayBase { - using Strings for address; + using StringsUnreleased for address; function supportsAttribute(bytes4 /*selector*/) public view virtual returns (bool) { return false; @@ -29,7 +29,7 @@ abstract contract AxelarGatewaySource is IGatewaySource, AxelarGatewayBase { } // Create the package - string memory sender = msg.sender.toHexString(); + string memory sender = msg.sender.toChecksumHexString(); bytes memory adapterPayload = abi.encode(sender, receiver, payload, attributes); // Emit event diff --git a/contracts/crosschain/mocks/AxelarGatewayMock.sol b/contracts/crosschain/mocks/AxelarGatewayMock.sol index 6c2424f8..50ba7a96 100644 --- a/contracts/crosschain/mocks/AxelarGatewayMock.sol +++ b/contracts/crosschain/mocks/AxelarGatewayMock.sol @@ -5,11 +5,11 @@ pragma solidity ^0.8.27; import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol"; import {IAxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarExecutable.sol"; import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol"; -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {StringsUnreleased} from "../../utils/Strings.sol"; contract AxelarGatewayMock { - using Strings for address; + using StringsUnreleased for address; + using StringsUnreleased for string; using BitMaps for BitMaps.BitMap; bool private activeMode; @@ -42,7 +42,12 @@ contract AxelarGatewayMock { ); bytes32 commandId = keccak256( - abi.encode(destinationChain, msg.sender.toHexString(), destinationContractAddress, keccak256(payload)) + abi.encode( + destinationChain, + msg.sender.toChecksumHexString(), + destinationContractAddress, + keccak256(payload) + ) ); require(!pendingCommandIds.get(uint256(commandId))); @@ -53,8 +58,8 @@ contract AxelarGatewayMock { if (activeMode) { // NOTE: // - source chain and destination chain are the same in this mock - address target = StringsUnreleased.parseAddress(destinationContractAddress); - IAxelarExecutable(target).execute(commandId, destinationChain, msg.sender.toHexString(), payload); + address target = destinationContractAddress.parseAddress(); + IAxelarExecutable(target).execute(commandId, destinationChain, msg.sender.toChecksumHexString(), payload); } } @@ -70,7 +75,8 @@ contract AxelarGatewayMock { emit IAxelarGateway.ContractCallExecuted(commandId); return - commandId == keccak256(abi.encode(sourceChain, sourceAddress, msg.sender.toHexString(), payloadHash)); + commandId == + keccak256(abi.encode(sourceChain, sourceAddress, msg.sender.toChecksumHexString(), payloadHash)); } else return false; } } diff --git a/contracts/utils/CAIP-10.sol b/contracts/utils/CAIP-10.sol index 70ced580..c41712fc 100644 --- a/contracts/utils/CAIP-10.sol +++ b/contracts/utils/CAIP-10.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {StringsUnreleased} from "./Strings.sol"; import {Bytes} from "./Bytes.sol"; import {CAIP2} from "./CAIP-2.sol"; @@ -12,11 +12,11 @@ import {CAIP2} from "./CAIP-2.sol"; // account_address: [-.%a-zA-Z0-9]{1,128} library CAIP10 { using SafeCast for uint256; - using Strings for address; + using StringsUnreleased for address; using Bytes for bytes; function local(address account) internal view returns (string memory) { - return format(CAIP2.local(), account.toHexString()); + return format(CAIP2.local(), account.toChecksumHexString()); } function format(string memory caip2, string memory account) internal pure returns (string memory) { diff --git a/test/crosschain/axelar/AxelarGateway.test.js b/test/crosschain/axelar/AxelarGateway.test.js index d1ccda71..85c0561c 100644 --- a/test/crosschain/axelar/AxelarGateway.test.js +++ b/test/crosschain/axelar/AxelarGateway.test.js @@ -3,7 +3,7 @@ const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs'); -const getAddress = account => (account.target ?? account.address ?? account).toLowerCase(); +const getAddress = account => ethers.getAddress(account.target ?? account.address ?? account); async function fixture() { const [owner, sender, ...accounts] = await ethers.getSigners(); diff --git a/test/crosschain/utils/CAIP.test.js b/test/crosschain/utils/CAIP.test.js index c649354f..b1da211d 100644 --- a/test/crosschain/utils/CAIP.test.js +++ b/test/crosschain/utils/CAIP.test.js @@ -37,7 +37,7 @@ describe('CAIP utilities', function () { it(`local(${account})`, async function () { // lowercase encoding for now - expect(await this.caip10.$local(ethers.Typed.address(account))).to.equal(format('eip155', this.chainId, account.toLowerCase())); + expect(await this.caip10.$local(ethers.Typed.address(account))).to.equal(format('eip155', this.chainId, account)); }); for (const { account, caip2, caip10 } of Object.values(CHAINS)) From 86caeeb4bd0e1c808d8c666f3ebb1ee5887378c5 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 22 Oct 2024 16:38:03 +0200 Subject: [PATCH 40/51] use @openzepplin/contracts@master --- .../axelar/AxelarGatewayDestination.sol | 18 +- .../crosschain/axelar/AxelarGatewaySource.sol | 15 +- .../interfaces/IGatewayDestinationPassive.sol | 15 - .../interfaces/IGatewayReceiver.sol | 14 - .../crosschain/interfaces/IGatewaySource.sol | 26 -- .../crosschain/mocks/AxelarGatewayMock.sol | 6 +- .../crosschain/mocks/ERC7786ReceiverMock.sol | 29 ++ .../crosschain/mocks/GatewayReceiverMock.sol | 46 --- .../vendor/draft-ERC7786Receiver.sol | 62 +++ .../crosschain/vendor/draft-IERC7786.sol | 99 +++++ contracts/utils/Bytes.sol | 108 ----- contracts/utils/CAIP-10.sol | 32 -- contracts/utils/CAIP-2.sol | 31 -- contracts/utils/Set.sol | 32 -- contracts/utils/Strings.sol | 390 ------------------ lib/@openzeppelin-contracts | 2 +- lib/@openzeppelin-contracts-upgradeable | 2 +- package-lock.json | 390 ++++++++++++++++++ package.json | 2 + test/crosschain/axelar/AxelarGateway.test.js | 6 +- test/crosschain/chains.js | 93 ----- test/crosschain/utils/CAIP.test.js | 54 --- 22 files changed, 607 insertions(+), 865 deletions(-) delete mode 100644 contracts/crosschain/interfaces/IGatewayDestinationPassive.sol delete mode 100644 contracts/crosschain/interfaces/IGatewayReceiver.sol delete mode 100644 contracts/crosschain/interfaces/IGatewaySource.sol create mode 100644 contracts/crosschain/mocks/ERC7786ReceiverMock.sol delete mode 100644 contracts/crosschain/mocks/GatewayReceiverMock.sol create mode 100644 contracts/crosschain/vendor/draft-ERC7786Receiver.sol create mode 100644 contracts/crosschain/vendor/draft-IERC7786.sol delete mode 100644 contracts/utils/Bytes.sol delete mode 100644 contracts/utils/CAIP-10.sol delete mode 100644 contracts/utils/CAIP-2.sol delete mode 100644 contracts/utils/Set.sol delete mode 100644 contracts/utils/Strings.sol delete mode 100644 test/crosschain/chains.js delete mode 100644 test/crosschain/utils/CAIP.test.js diff --git a/contracts/crosschain/axelar/AxelarGatewayDestination.sol b/contracts/crosschain/axelar/AxelarGatewayDestination.sol index 27a68ae6..fb979b73 100644 --- a/contracts/crosschain/axelar/AxelarGatewayDestination.sol +++ b/contracts/crosschain/axelar/AxelarGatewayDestination.sol @@ -3,16 +3,16 @@ pragma solidity ^0.8.27; import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol"; -import {StringsUnreleased} from "../../utils/Strings.sol"; -import {CAIP2} from "../../utils/CAIP-2.sol"; -import {CAIP10} from "../../utils/CAIP-10.sol"; +import {CAIP2} from "@openzeppelin/contracts@master/utils/CAIP2.sol"; +import {CAIP10} from "@openzeppelin/contracts@master/utils/CAIP10.sol"; +import {Strings} from "@openzeppelin/contracts@master/utils/Strings.sol"; import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; -import {IGatewayDestinationPassive} from "../interfaces/IGatewayDestinationPassive.sol"; -import {IGatewayReceiver} from "../interfaces/IGatewayReceiver.sol"; +// import {IERC7786GatewayDestinationPassive, IERC7786Receiver} from "@openzeppelin/contracts@master/interfaces/IERC7786.sol"; +import {IERC7786GatewayDestinationPassive, IERC7786Receiver} from "../vendor/draft-IERC7786.sol"; -abstract contract AxelarGatewayDestination is IGatewayDestinationPassive, AxelarGatewayBase, AxelarExecutable { - using StringsUnreleased for address; - using StringsUnreleased for string; +abstract contract AxelarGatewayDestination is IERC7786GatewayDestinationPassive, AxelarGatewayBase, AxelarExecutable { + using Strings for address; + using Strings for string; /// @dev Passive mode function validateReceivedMessage( @@ -70,7 +70,7 @@ abstract contract AxelarGatewayDestination is IGatewayDestinationPassive, Axelar require(getRemoteGateway(source).equal(remoteAccount), "Invalid origin gateway"); // Active mode - IGatewayReceiver(receiver.parseAddress()).receiveMessage( + IERC7786Receiver(receiver.parseAddress()).receiveMessage( address(0), // not needed in active mode new bytes(0), // not needed in active mode source, diff --git a/contracts/crosschain/axelar/AxelarGatewaySource.sol b/contracts/crosschain/axelar/AxelarGatewaySource.sol index a4cfc125..1e81b09c 100644 --- a/contracts/crosschain/axelar/AxelarGatewaySource.sol +++ b/contracts/crosschain/axelar/AxelarGatewaySource.sol @@ -2,14 +2,15 @@ pragma solidity ^0.8.27; +import {CAIP2} from "@openzeppelin/contracts@master/utils/CAIP2.sol"; +import {CAIP10} from "@openzeppelin/contracts@master/utils/CAIP10.sol"; +import {Strings} from "@openzeppelin/contracts@master/utils/Strings.sol"; import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; -import {IGatewaySource} from "../interfaces/IGatewaySource.sol"; -import {StringsUnreleased} from "../../utils/Strings.sol"; -import {CAIP2} from "../../utils/CAIP-2.sol"; -import {CAIP10} from "../../utils/CAIP-10.sol"; +// import {IERC7786GatewaySource} from "@openzeppelin/contracts@master/interfaces/IERC7786.sol"; +import {IERC7786GatewaySource} from "../vendor/draft-IERC7786.sol"; -abstract contract AxelarGatewaySource is IGatewaySource, AxelarGatewayBase { - using StringsUnreleased for address; +abstract contract AxelarGatewaySource is IERC7786GatewaySource, AxelarGatewayBase { + using Strings for address; function supportsAttribute(bytes4 /*selector*/) public view virtual returns (bool) { return false; @@ -25,7 +26,7 @@ abstract contract AxelarGatewaySource is IGatewaySource, AxelarGatewayBase { require(msg.value == 0, "Value not supported"); for (uint256 i = 0; i < attributes.length; ++i) { bytes4 selector = bytes4(attributes[i][0:4]); - require(supportsAttribute(selector), UnsuportedAttribute(selector)); + require(supportsAttribute(selector), UnsupportedAttribute(selector)); } // Create the package diff --git a/contracts/crosschain/interfaces/IGatewayDestinationPassive.sol b/contracts/crosschain/interfaces/IGatewayDestinationPassive.sol deleted file mode 100644 index 87a640a2..00000000 --- a/contracts/crosschain/interfaces/IGatewayDestinationPassive.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -interface IGatewayDestinationPassive { - error InvalidMessageKey(bytes messageKey); - - function validateReceivedMessage( - bytes calldata messageKey, - string calldata source, - string calldata sender, - bytes calldata payload, - bytes[] calldata attributes - ) external; -} diff --git a/contracts/crosschain/interfaces/IGatewayReceiver.sol b/contracts/crosschain/interfaces/IGatewayReceiver.sol deleted file mode 100644 index d35f7843..00000000 --- a/contracts/crosschain/interfaces/IGatewayReceiver.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -interface IGatewayReceiver { - function receiveMessage( - address gateway, - bytes calldata gatewayMessageKey, - string calldata source, - string calldata sender, - bytes calldata payload, - bytes[] calldata attributes - ) external payable; -} diff --git a/contracts/crosschain/interfaces/IGatewaySource.sol b/contracts/crosschain/interfaces/IGatewaySource.sol deleted file mode 100644 index ec11030a..00000000 --- a/contracts/crosschain/interfaces/IGatewaySource.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -interface IGatewaySource { - event MessageCreated( - bytes32 outboxId, - string sender, // CAIP-10 account ID - string receiver, // CAIP-10 account ID - bytes payload, - bytes[] attributes - ); - - event MessageSent(bytes32 indexed outboxId); - - error UnsuportedAttribute(bytes4 signature); - - function supportsAttribute(bytes4 signature) external view returns (bool); - - function sendMessage( - string calldata destination, // CAIP-2 chain ID - string calldata receiver, // CAIP-10 account ID - bytes calldata payload, - bytes[] calldata attributes - ) external payable returns (bytes32 outboxId); -} diff --git a/contracts/crosschain/mocks/AxelarGatewayMock.sol b/contracts/crosschain/mocks/AxelarGatewayMock.sol index 50ba7a96..7f9c5c77 100644 --- a/contracts/crosschain/mocks/AxelarGatewayMock.sol +++ b/contracts/crosschain/mocks/AxelarGatewayMock.sol @@ -5,11 +5,11 @@ pragma solidity ^0.8.27; import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol"; import {IAxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarExecutable.sol"; import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol"; -import {StringsUnreleased} from "../../utils/Strings.sol"; +import {Strings} from "@openzeppelin/contracts@master/utils/Strings.sol"; contract AxelarGatewayMock { - using StringsUnreleased for address; - using StringsUnreleased for string; + using Strings for address; + using Strings for string; using BitMaps for BitMaps.BitMap; bool private activeMode; diff --git a/contracts/crosschain/mocks/ERC7786ReceiverMock.sol b/contracts/crosschain/mocks/ERC7786ReceiverMock.sol new file mode 100644 index 00000000..e6356fc2 --- /dev/null +++ b/contracts/crosschain/mocks/ERC7786ReceiverMock.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.27; + +import {ERC7786Receiver} from "../vendor/draft-ERC7786Receiver.sol"; + +contract ERC7786ReceiverMock is ERC7786Receiver { + address public immutable GATEWAY; + + event MessageReceived(address gateway, string source, string sender, bytes payload, bytes[] attributes); + + constructor(address _gateway) { + GATEWAY = _gateway; + } + + function _isKnownGateway(address instance) internal view virtual override returns (bool) { + return instance == GATEWAY; + } + + function _processMessage( + address gateway, + string calldata source, + string calldata sender, + bytes calldata payload, + bytes[] calldata attributes + ) internal virtual override { + emit MessageReceived(gateway, source, sender, payload, attributes); + } +} diff --git a/contracts/crosschain/mocks/GatewayReceiverMock.sol b/contracts/crosschain/mocks/GatewayReceiverMock.sol deleted file mode 100644 index f0a6becf..00000000 --- a/contracts/crosschain/mocks/GatewayReceiverMock.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.27; - -import {IGatewayDestinationPassive} from "../interfaces/IGatewayDestinationPassive.sol"; -import {IGatewayReceiver} from "../interfaces/IGatewayReceiver.sol"; - -contract GatewayReceiverMock is IGatewayReceiver { - address public immutable GATEWAY; - - event MessageReceived(bytes gatewayMessageKey, string source, string sender, bytes payload, bytes[] attributes); - - constructor(address _gateway) { - GATEWAY = _gateway; - } - - function isGateway(address instance) public view returns (bool) { - return instance == GATEWAY; - } - - function receiveMessage( - address gateway, - bytes calldata gatewayMessageKey, - string calldata source, - string calldata sender, - bytes calldata payload, - bytes[] calldata attributes - ) public payable { - if (isGateway(msg.sender)) { - // Active mode - // no extra check - } else if (isGateway(gateway)) { - // Passive mode - IGatewayDestinationPassive(gateway).validateReceivedMessage( - gatewayMessageKey, - source, - sender, - payload, - attributes - ); - } else { - revert("invalid call"); - } - emit MessageReceived(gatewayMessageKey, source, sender, payload, attributes); - } -} diff --git a/contracts/crosschain/vendor/draft-ERC7786Receiver.sol b/contracts/crosschain/vendor/draft-ERC7786Receiver.sol new file mode 100644 index 00000000..33c2dea1 --- /dev/null +++ b/contracts/crosschain/vendor/draft-ERC7786Receiver.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {IERC7786GatewayDestinationPassive, IERC7786Receiver} from "./draft-IERC7786.sol"; + +/** + * @dev Base implementation of an ERC-7786 compliant cross-chain message receiver. + * + * This abstract contract exposes the `receiveMessage` function that is used in both active and passive mode for + * communication with (one or multiple) destination gateways. This contract leaves two function unimplemented: + * + * {_isKnownGateway}, an internal getter used to verify whether an address is recognised by the contract as a valid + * ERC-7786 destination gateway. One or multiple gateway can be supported. Note that any malicious address for which + * this function returns true would be able to impersonate any account on any other chain sending any message. + * + * {_processMessage}, the internal function that will be called with any message that has been validated. + */ +abstract contract ERC7786Receiver is IERC7786Receiver { + error ERC7786ReceiverInvalidGateway(address gateway); + error ERC7786ReceivePassiveModeValue(); + + /// @inheritdoc IERC7786Receiver + function receiveMessage( + address gateway, + bytes calldata gatewayMessageKey, + string calldata source, + string calldata sender, + bytes calldata payload, + bytes[] calldata attributes + ) public payable virtual { + if (_isKnownGateway(msg.sender)) { + // Active mode + // no extra check + } else if (_isKnownGateway(gateway)) { + // Passive mode + if (msg.value != 0) revert ERC7786ReceivePassiveModeValue(); + IERC7786GatewayDestinationPassive(gateway).validateReceivedMessage( + gatewayMessageKey, + source, + sender, + payload, + attributes + ); + } else { + revert ERC7786ReceiverInvalidGateway(gateway); + } + _processMessage(gateway, source, sender, payload, attributes); + } + + /// @dev Virtual getter that returns whether an address in a valid ERC-7786 gateway. + function _isKnownGateway(address instance) internal view virtual returns (bool); + + /// @dev Virtual function that should contain the logic to execute when a cross-chain message is received. + function _processMessage( + address gateway, + string calldata source, + string calldata sender, + bytes calldata payload, + bytes[] calldata attributes + ) internal virtual; +} diff --git a/contracts/crosschain/vendor/draft-IERC7786.sol b/contracts/crosschain/vendor/draft-IERC7786.sol new file mode 100644 index 00000000..d3c617c4 --- /dev/null +++ b/contracts/crosschain/vendor/draft-IERC7786.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/** + * @dev Interface for ERC-7786 source gateways. + * + * See ERC-7786 for more details + */ +interface IERC7786GatewaySource { + /** + * @dev Event emitted when a message is created. If `outboxId` is zero, no further processing is necessary, and + * no {MessageSent} event SHOULD be expected. If `outboxId` is not zero, then further (gateway specific, and non + * standardized) action is required. + */ + event MessageCreated( + bytes32 indexed outboxId, + string sender, // CAIP-10 account ID + string receiver, // CAIP-10 account ID + bytes payload, + bytes[] attributes + ); + + /** + * @dev This event is emitted when a message, for which the {MessageCreated} event contains an non zero `outboxId`, + * received the required post processing actions, and was thus sent to the destination chain. + */ + event MessageSent(bytes32 indexed outboxId); + + /// @dev This error is thrown when a message creation fails because of an unsupported attribute being specified. + error UnsupportedAttribute(bytes4 selector); + + /// @dev Getter to check whether an attribute is supported or not. + function supportsAttribute(bytes4 selector) external view returns (bool); + + /** + * @dev Endpoint for creating a new message. If the message requires further (gateway specific) processing before + * it can be sent to the destination chain, then a non-zero `outboxId` must be returned. Otherwise, the + * message MUST be sent and this function must return 0. + * + * * MUST emit a {MessageCreated} event. + * * SHOULD NOT emit a {MessageSent} event. + * + * If any of the `attributes` is not supported, this function SHOULD revert with an {UnsupportedAttribute} error. + * Other errors SHOULD revert with errors not specified in ERC-7786. + */ + function sendMessage( + string calldata destination, // CAIP-2 chain ID + string calldata receiver, // CAIP-10 account ID + bytes calldata payload, + bytes[] calldata attributes + ) external payable returns (bytes32 outboxId); +} + +/** + * @dev Interface for ERC-7786 destination gateways operating in passive mode. + * + * See ERC-7786 for more details + */ +interface IERC7786GatewayDestinationPassive { + error InvalidMessageKey(bytes messageKey); + + /** + * @dev Endpoint for checking the validity of a message that is being relayed in passive mode. The message + * receiver is implicitly the caller of this method, which guarantees that no-one but the receiver can + * "consume" the message. This function MUST implement replay protection, meaning that if called multiple time + * for same message, all but the first calls MUST revert. + * + * NOTE: implementing this interface is OPTIONAL. Some destination gateway MAY only support active mode. + */ + function validateReceivedMessage( + bytes calldata messageKey, + string calldata source, + string calldata sender, + bytes calldata payload, + bytes[] calldata attributes + ) external; +} + +/** + * @dev Interface for the ERC-7786 client contracts (receiver). + * + * See ERC-7786 for more details + */ +interface IERC7786Receiver { + /** + * @dev Endpoint for receiving cross-chain message. + * + * This function may be called directly by the gateway (active mode) or by a third party (passive mode). + */ + function receiveMessage( + address gateway, + bytes calldata gatewayMessageKey, + string calldata source, + string calldata sender, + bytes calldata payload, + bytes[] calldata attributes + ) external payable; +} diff --git a/contracts/utils/Bytes.sol b/contracts/utils/Bytes.sol deleted file mode 100644 index 2f7e09a0..00000000 --- a/contracts/utils/Bytes.sol +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; - -/** - * @dev Bytes operations. - */ -library Bytes { - /** - * @dev Forward search for `s` in `buffer` - * * If `s` is present in the buffer, returns the index of the first instance - * * If `s` is not present in the buffer, returns type(uint256).max - * - * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf - */ - function indexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) { - return indexOf(buffer, s, 0); - } - - /** - * @dev Forward search for `s` in `buffer` starting at position `pos` - * * If `s` is present in the buffer (at or after `pos`), returns the index of the next instance - * * If `s` is not present in the buffer (at or after `pos`), returns the length of the buffer - * - * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf - */ - function indexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { - unchecked { - uint256 length = buffer.length; - for (uint256 i = pos; i < length; ++i) { - if (buffer[i] == s) { - return i; - } - } - return type(uint256).max; - } - } - - /** - * @dev Backward search for `s` in `buffer` - * * If `s` is present in the buffer, returns the index of the last instance - * * If `s` is not present in the buffer, returns type(uint256).max - * - * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf - */ - function lastIndexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) { - return lastIndexOf(buffer, s, buffer.length - 1); - } - - /** - * @dev Backward search for `s` in `buffer` starting at position `pos` - * * If `s` is present in the buffer (at or before `pos`), returns the index of the previous instance - * * If `s` is not present in the buffer (at or before `pos`), returns the length of the buffer - * - * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf - */ - function lastIndexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { - unchecked { - for (uint256 i = pos + 1; i > 0; --i) { - if (buffer[i - 1] == s) { - return i - 1; - } - } - return type(uint256).max; - } - } - - /** - * @dev Copies the content of `buffer`, from `start` (included) to the end of `buffer` into a new bytes object in - * memory. - * - * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice - */ - function slice(bytes memory buffer, uint256 start) internal pure returns (bytes memory) { - return slice(buffer, start, buffer.length); - } - - /** - * @dev Copies the content of `buffer`, from `start` (included) to `end` (excluded) into a new bytes object in - * memory. - * - * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice - */ - function slice(bytes memory buffer, uint256 start, uint256 end) internal pure returns (bytes memory) { - // sanitize - uint256 length = buffer.length; - start = Math.min(start, length); - end = Math.min(end, length); - - // allocate and copy - bytes memory result = new bytes(end - start); - assembly ("memory-safe") { - mcopy(add(result, 0x20), add(buffer, add(start, 0x20)), sub(end, start)) - } - - return result; - } - - /// @dev Reads a bytes32 from a bytes array without bounds checking. - function unsafeReadBytesOffset(bytes memory buffer, uint256 offset) internal pure returns (bytes32 value) { - // This is not memory safe in the general case - assembly { - value := mload(add(buffer, add(0x20, offset))) - } - } -} diff --git a/contracts/utils/CAIP-10.sol b/contracts/utils/CAIP-10.sol deleted file mode 100644 index c41712fc..00000000 --- a/contracts/utils/CAIP-10.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import {StringsUnreleased} from "./Strings.sol"; -import {Bytes} from "./Bytes.sol"; -import {CAIP2} from "./CAIP-2.sol"; - -// account_id: chain_id + ":" + account_address -// chain_id: [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See [CAIP-2][]) -// account_address: [-.%a-zA-Z0-9]{1,128} -library CAIP10 { - using SafeCast for uint256; - using StringsUnreleased for address; - using Bytes for bytes; - - function local(address account) internal view returns (string memory) { - return format(CAIP2.local(), account.toChecksumHexString()); - } - - function format(string memory caip2, string memory account) internal pure returns (string memory) { - return string.concat(caip2, ":", account); - } - - function parse(string memory caip10) internal pure returns (string memory caip2, string memory account) { - bytes memory buffer = bytes(caip10); - - uint256 pos = buffer.lastIndexOf(":"); - return (string(buffer.slice(0, pos)), string(buffer.slice(pos + 1))); - } -} diff --git a/contracts/utils/CAIP-2.sol b/contracts/utils/CAIP-2.sol deleted file mode 100644 index 9c79d48c..00000000 --- a/contracts/utils/CAIP-2.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {Bytes} from "./Bytes.sol"; - -// chain_id: namespace + ":" + reference -// namespace: [-a-z0-9]{3,8} -// reference: [-_a-zA-Z0-9]{1,32} -library CAIP2 { - using SafeCast for uint256; - using Strings for uint256; - using Bytes for bytes; - - function local() internal view returns (string memory) { - return format("eip155", block.chainid.toString()); - } - - function format(string memory namespace, string memory ref) internal pure returns (string memory) { - return string.concat(namespace, ":", ref); - } - - function parse(string memory caip2) internal pure returns (string memory namespace, string memory ref) { - bytes memory buffer = bytes(caip2); - - uint256 pos = buffer.indexOf(":"); - return (string(buffer.slice(0, pos)), string(buffer.slice(pos + 1))); - } -} diff --git a/contracts/utils/Set.sol b/contracts/utils/Set.sol deleted file mode 100644 index 1a952162..00000000 --- a/contracts/utils/Set.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -/// @dev non-iterable variant of OpenZeppelin's EnumerableSet library. -library Set { - struct Bytes32Set { - mapping(bytes32 value => bool) _data; - } - - function insert(Bytes32Set storage self, bytes32 value) internal returns (bool) { - if (!self._data[value]) { - self._data[value] = true; - return true; - } else { - return false; - } - } - - function remove(Bytes32Set storage self, bytes32 value) internal returns (bool) { - if (self._data[value]) { - self._data[value] = false; - return true; - } else { - return false; - } - } - - function contains(Bytes32Set storage self, bytes32 value) internal view returns (bool) { - return self._data[value]; - } -} diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol deleted file mode 100644 index a6f9fc62..00000000 --- a/contracts/utils/Strings.sol +++ /dev/null @@ -1,390 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol) - -pragma solidity ^0.8.20; - -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import {SignedMath} from "@openzeppelin/contracts/utils/math/SignedMath.sol"; -import {Bytes} from "./Bytes.sol"; - -/** - * @dev String operations. - */ -library StringsUnreleased { - using Bytes for bytes; - using SafeCast for *; - - bytes16 private constant HEX_DIGITS = "0123456789abcdef"; - uint8 private constant ADDRESS_LENGTH = 20; - - /** - * @dev The `value` string doesn't fit in the specified `length`. - */ - error StringsInsufficientHexLength(uint256 value, uint256 length); - - /** - * @dev The string being parsed contains characters that are not in scope of the given base. - */ - error StringsInvalidChar(); - - /** - * @dev The string being parsed is not a properly formatted address. - */ - error StringsInvalidAddressFormat(); - - /** - * @dev Converts a `uint256` to its ASCII `string` decimal representation. - */ - function toString(uint256 value) internal pure returns (string memory) { - unchecked { - uint256 length = Math.log10(value) + 1; - string memory buffer = new string(length); - uint256 ptr; - /// @solidity memory-safe-assembly - assembly { - ptr := add(buffer, add(32, length)) - } - while (true) { - ptr--; - /// @solidity memory-safe-assembly - assembly { - mstore8(ptr, byte(mod(value, 10), HEX_DIGITS)) - } - value /= 10; - if (value == 0) break; - } - return buffer; - } - } - - /** - * @dev Converts a `int256` to its ASCII `string` decimal representation. - */ - function toStringSigned(int256 value) internal pure returns (string memory) { - return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value))); - } - - /** - * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. - */ - function toHexString(uint256 value) internal pure returns (string memory) { - unchecked { - return toHexString(value, Math.log256(value) + 1); - } - } - - /** - * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. - */ - function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { - uint256 localValue = value; - bytes memory buffer = new bytes(2 * length + 2); - buffer[0] = "0"; - buffer[1] = "x"; - for (uint256 i = 2 * length + 1; i > 1; --i) { - buffer[i] = HEX_DIGITS[localValue & 0xf]; - localValue >>= 4; - } - if (localValue != 0) { - revert StringsInsufficientHexLength(value, length); - } - return string(buffer); - } - - /** - * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal - * representation. - */ - function toHexString(address addr) internal pure returns (string memory) { - return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH); - } - - /** - * @dev Converts an `address` with fixed length of 20 bytes to its checksummed ASCII `string` hexadecimal - * representation, according to EIP-55. - */ - function toChecksumHexString(address addr) internal pure returns (string memory) { - bytes memory buffer = bytes(toHexString(addr)); - - // hash the hex part of buffer (skip length + 2 bytes, length 40) - uint256 hashValue; - assembly ("memory-safe") { - hashValue := shr(96, keccak256(add(buffer, 0x22), 40)) - } - - for (uint256 i = 41; i > 1; --i) { - // possible values for buffer[i] are 48 (0) to 57 (9) and 97 (a) to 102 (f) - if (hashValue & 0xf > 7 && uint8(buffer[i]) > 96) { - // case shift by xoring with 0x20 - buffer[i] ^= 0x20; - } - hashValue >>= 4; - } - return string(buffer); - } - - /** - * @dev Returns true if the two strings are equal. - */ - function equal(string memory a, string memory b) internal pure returns (bool) { - return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b)); - } - - /** - * @dev Parse a decimal string and returns the value as a `uint256`. - * - * Requirements: - * - The string must be formatted as `[0-9]*` - * - The result must fit into an `uint256` type - */ - function parseUint(string memory input) internal pure returns (uint256) { - return parseUint(input, 0, bytes(input).length); - } - - /** - * @dev Variant of {parseUint} that parses a substring of `input` located between position `begin` (included) and - * `end` (excluded). - * - * Requirements: - * - The substring must be formatted as `[0-9]*` - * - The result must fit into an `uint256` type - */ - function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) { - (bool success, uint256 value) = tryParseUint(input, begin, end); - if (!success) revert StringsInvalidChar(); - return value; - } - - /** - * @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character. - * - * Requirements: - * - The result must fit into an `uint256` type. - */ - function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) { - return tryParseUint(input, 0, bytes(input).length); - } - - /** - * @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid - * character. - * - * Requirements: - * - The result must fit into an `uint256` type. - */ - function tryParseUint( - string memory input, - uint256 begin, - uint256 end - ) internal pure returns (bool success, uint256 value) { - bytes memory buffer = bytes(input); - - uint256 result = 0; - for (uint256 i = begin; i < end; ++i) { - uint8 chr = _tryParseChr(buffer[i]); - if (chr > 9) return (false, 0); - result *= 10; - result += chr; - } - return (true, result); - } - - /** - * @dev Parse a decimal string and returns the value as a `int256`. - * - * Requirements: - * - The string must be formatted as `[-+]?[0-9]*` - * - The result must fit in an `int256` type. - */ - function parseInt(string memory input) internal pure returns (int256) { - return parseInt(input, 0, bytes(input).length); - } - - /** - * @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and - * `end` (excluded). - * - * Requirements: - * - The substring must be formatted as `[-+]?[0-9]*` - * - The result must fit in an `int256` type. - */ - function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) { - (bool success, int256 value) = tryParseInt(input, begin, end); - if (!success) revert StringsInvalidChar(); - return value; - } - - /** - * @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character. - * - * Requirements: - * - The result must fit into an `int256` type. - */ - function tryParseInt(string memory input) internal pure returns (bool success, int256 value) { - return tryParseInt(input, 0, bytes(input).length); - } - - /** - * @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid - * character. - * - * This function will still revert if the result does not fit in a `int256` - */ - function tryParseInt( - string memory input, - uint256 begin, - uint256 end - ) internal pure returns (bool success, int256 value) { - bytes memory buffer = bytes(input); - - // check presence of a negative sign. - bool positiveSign = bytes1(buffer.unsafeReadBytesOffset(begin)) == bytes1("+"); - bool negativeSign = bytes1(buffer.unsafeReadBytesOffset(begin)) == bytes1("-"); - uint256 offset = (positiveSign || negativeSign) ? 1 : 0; - int8 factor = negativeSign ? int8(-1) : int8(1); - - int256 result = 0; - for (uint256 i = begin + offset; i < end; ++i) { - uint8 chr = _tryParseChr(buffer[i]); - if (chr > 9) return (false, 0); - result *= 10; - result += factor * int8(chr); - } - return (true, result); - } - - /** - * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`. - * - * Requirements: - * - The string must be formatted as `(0x)?[0-9a-fA-F]*` - * - The result must fit in an `uint256` type. - */ - function parseHex(string memory input) internal pure returns (uint256) { - return parseHex(input, 0, bytes(input).length); - } - - /** - * @dev Variant of {parseHex} that parses a substring of `input` located between position `begin` (included) and - * `end` (excluded). - * - * Requirements: - * - The substring must be formatted as `(0x)?[0-9a-fA-F]*` - * - The result must fit in an `uint256` type. - */ - function parseHex(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) { - (bool success, uint256 value) = tryParseHex(input, begin, end); - if (!success) revert StringsInvalidChar(); - return value; - } - - /** - * @dev Variant of {parseHex-string} that returns false if the parsing fails because of an invalid character. - * - * Requirements: - * - The result must fit into an `uint256` type. - */ - function tryParseHex(string memory input) internal pure returns (bool success, uint256 value) { - return tryParseHex(input, 0, bytes(input).length); - } - - /** - * @dev Variant of {parseHex-string-uint256-uint256} that returns false if the parsing fails because of an - * invalid character. - * - * Requirements: - * - The result must fit into an `uint256` type. - */ - function tryParseHex( - string memory input, - uint256 begin, - uint256 end - ) internal pure returns (bool success, uint256 value) { - bytes memory buffer = bytes(input); - - // skip 0x prefix if present - bool hasPrefix = bytes2(buffer.unsafeReadBytesOffset(begin)) == bytes2("0x"); - uint256 offset = hasPrefix ? 2 : 0; - - uint256 result = 0; - for (uint256 i = begin + offset; i < end; ++i) { - uint8 chr = _tryParseChr(buffer[i]); - if (chr > 15) return (false, 0); - result *= 16; - result += chr; - } - return (true, result); - } - - /** - * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`. - * - * Requirements: - * - The string must be formatted as `(0x)?[0-9a-fA-F]{40}` - */ - function parseAddress(string memory input) internal pure returns (address) { - return parseAddress(input, 0, bytes(input).length); - } - - /** - * @dev Variant of {parseAddress} that parses a substring of `input` located between position `begin` (included) and - * `end` (excluded). - * - * Requirements: - * - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}` - */ - function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) { - (bool success, address value) = tryParseAddress(input, begin, end); - if (!success) revert StringsInvalidAddressFormat(); - return value; - } - - /** - * @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly - * formatted address. See {parseAddress} requirements. - */ - function tryParseAddress(string memory input) internal pure returns (bool success, address value) { - return tryParseAddress(input, 0, bytes(input).length); - } - - /** - * @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly - * formatted address. See {parseAddress} requirements. - */ - function tryParseAddress( - string memory input, - uint256 begin, - uint256 end - ) internal pure returns (bool success, address value) { - // check that input is the correct length - bool hasPrefix = bytes2(bytes(input).unsafeReadBytesOffset(begin)) == 0x3078; - uint256 expectedLength = hasPrefix ? 42 : 40; - - if (end - begin == expectedLength) { - // length guarantees that this does not overflow, and value2 is at most type(uint160).max - (bool s, uint256 v) = tryParseHex(input, begin, end); - return (s, address(uint160(v))); - } else { - return (false, address(0)); - } - } - - function _tryParseChr(bytes1 chr) private pure returns (uint8) { - uint8 value = uint8(chr); - - // Try to parse `chr`: - // - Case 1: [0-9] - // - Case 2: [a-f] - // - Case 3: [A-F] - // - otherwise not supported - unchecked { - if (value > 47 && value < 58) value -= 48; - else if (value > 96 && value < 103) value -= 87; - else if (value > 64 && value < 71) value -= 55; - else return type(uint8).max; - } - - return value; - } -} diff --git a/lib/@openzeppelin-contracts b/lib/@openzeppelin-contracts index 52c36d41..bc1df46e 160000 --- a/lib/@openzeppelin-contracts +++ b/lib/@openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 52c36d412e8681053975396223d0ea39687fe33b +Subproject commit bc1df46eac3b897bc343952fccac3fad8a3095bc diff --git a/lib/@openzeppelin-contracts-upgradeable b/lib/@openzeppelin-contracts-upgradeable index 683b6729..0fd07cd9 160000 --- a/lib/@openzeppelin-contracts-upgradeable +++ b/lib/@openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit 683b6729cd061ceecf0ebdc29b00a01930fcc8e7 +Subproject commit 0fd07cd9a7d1b752323244176067a386eada4bc0 diff --git a/package-lock.json b/package-lock.json index 03da57aa..2c30f497 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "hardhat-exposed": "^0.3.15", "prettier": "^3.2.5", "prettier-plugin-solidity": "^1.3.1", + "rimraf": "^6.0.1", "solidity-coverage": "^0.8.12", "yargs": "^17.7.2" } @@ -504,6 +505,102 @@ "node": ">=14" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@metamask/eth-sig-util": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz", @@ -1792,6 +1889,35 @@ "sha.js": "^2.4.8" } }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/death": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/death/-/death-1.1.0.tgz", @@ -1887,6 +2013,12 @@ "node": ">=8" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/elliptic": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", @@ -2269,6 +2401,22 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fp-ts": { "version": "1.19.3", "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-1.19.3.tgz", @@ -2865,6 +3013,21 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/jackspeak": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", + "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -3073,6 +3236,15 @@ "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==", "dev": true }, + "node_modules/lru-cache": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", + "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", + "dev": true, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -3154,6 +3326,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -3596,6 +3777,12 @@ "node": ">=4" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, "node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -3614,12 +3801,37 @@ "node": ">=0.10.0" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -3853,6 +4065,72 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", @@ -4092,6 +4370,27 @@ "sha.js": "bin.js" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/shelljs": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", @@ -4109,6 +4408,18 @@ "node": ">=4" } }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -4304,6 +4615,21 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -4316,6 +4642,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-hex-prefix": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", @@ -4630,6 +4969,57 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", diff --git a/package.json b/package.json index 61fb6055..4ffafd9b 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "scripts": { "prepare": "git config --local core.hooksPath .githooks", "compile": "hardhat compile", + "clean": "hardhat clean && rimraf build contracts/build", "lint": "prettier --log-level warn --ignore-path .gitignore '{contracts,test}/**/*.sol' --check", "lint:fix": "prettier --log-level warn --ignore-path .gitignore '{contracts,test}/**/*.sol' --write", "coverage": "scripts/checks/coverage.sh", @@ -47,6 +48,7 @@ "hardhat-exposed": "^0.3.15", "prettier": "^3.2.5", "prettier-plugin-solidity": "^1.3.1", + "rimraf": "^6.0.1", "solidity-coverage": "^0.8.12", "yargs": "^17.7.2" } diff --git a/test/crosschain/axelar/AxelarGateway.test.js b/test/crosschain/axelar/AxelarGateway.test.js index 85c0561c..7a5fd8a0 100644 --- a/test/crosschain/axelar/AxelarGateway.test.js +++ b/test/crosschain/axelar/AxelarGateway.test.js @@ -15,7 +15,7 @@ async function fixture() { const axelar = await ethers.deployContract('$AxelarGatewayMock'); const srcGateway = await ethers.deployContract('$AxelarGatewaySource', [ owner, axelar ]); const dstGateway = await ethers.deployContract('$AxelarGatewayDestination', [ owner, axelar, axelar ]); - const receiver = await ethers.deployContract('$GatewayReceiverMock', [ dstGateway ]); + const receiver = await ethers.deployContract('$ERC7786ReceiverMock', [ dstGateway ]); await srcGateway.registerChainEquivalence(CAIP2, 'local'); await dstGateway.registerChainEquivalence(CAIP2, 'local'); @@ -57,7 +57,7 @@ describe('AxelarGateway', function () { .to.emit(this.srcGateway, 'MessageCreated').withArgs(ethers.ZeroHash, srcCAIP10, dstCAIP10, payload, attributes) .to.emit(this.axelar, 'ContractCall').withArgs(this.srcGateway, 'local', getAddress(this.dstGateway), ethers.keccak256(package), package) .to.emit(this.axelar, 'ContractCallExecuted').withArgs(anyValue) - .to.emit(this.receiver, 'MessageReceived').withArgs(anyValue, this.CAIP2, getAddress(this.sender), payload, attributes); + .to.emit(this.receiver, 'MessageReceived').withArgs(ethers.ZeroAddress, this.CAIP2, getAddress(this.sender), payload, attributes); }); }); @@ -92,7 +92,7 @@ describe('AxelarGateway', function () { attributes, )) .to.emit(this.axelar, 'ContractCallExecuted').withArgs(commandId) - .to.emit(this.receiver, 'MessageReceived').withArgs(commandId, this.CAIP2, getAddress(this.sender), payload, attributes); + .to.emit(this.receiver, 'MessageReceived').withArgs(this.dstGateway, this.CAIP2, getAddress(this.sender), payload, attributes); }); }); }); diff --git a/test/crosschain/chains.js b/test/crosschain/chains.js deleted file mode 100644 index be15cb92..00000000 --- a/test/crosschain/chains.js +++ /dev/null @@ -1,93 +0,0 @@ -const { ethers } = require('hardhat'); - -const mapValues = (obj, fn) => Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)])); -const format = (...args) => args.join(':'); - -// EVM (https://axelarscan.io/resources/chains?type=evm) -const ethereum = { - 'Ethereum': '1', - 'optimism': '10', - 'binance': '56', - 'Polygon': '137', - 'Fantom': '250', - 'fraxtal': '252', - 'filecoin': '314', - 'Moonbeam': '1284', - 'centrifuge': '2031', - 'kava': '2222', - 'mantle': '5000', - 'base': '8453', - 'immutable': '13371', - 'arbitrum': '42161', - 'celo': '42220', - 'Avalanche': '43114', - 'linea': '59144', - 'blast': '81457', - 'scroll': '534352', - 'aurora': '1313161554', -}; - -// Cosmos (https://axelarscan.io/resources/chains?type=cosmos) -const cosmos = { - 'Axelarnet': 'axelar-dojo-1', - 'osmosis': 'osmosis-1', - 'cosmoshub': 'cosmoshub-4', - 'juno': 'juno-1', - 'e-money': 'emoney-3', - 'injective': 'injective-1', - 'crescent': 'crescent-1', - 'kujira': 'kaiyo-1', - 'secret-snip': 'secret-4', - 'secret': 'secret-4', - 'sei': 'pacific-1', - 'stargaze': 'stargaze-1', - 'assetmantle': 'mantle-1', - 'fetch': 'fetchhub-4', - 'ki': 'kichain-2', - 'evmos': 'evmos_9001-2', - 'aura': 'xstaxy-1', - 'comdex': 'comdex-1', - 'persistence': 'core-1', - 'regen': 'regen-1', - 'umee': 'umee-1', - 'agoric': 'agoric-3', - 'xpla': 'dimension_37-1', - 'acre': 'acre_9052-1', - 'stride': 'stride-1', - 'carbon': 'carbon-1', - 'sommelier': 'sommelier-3', - 'neutron': 'neutron-1', - 'rebus': 'reb_1111-1', - 'archway': 'archway-1', - 'provenance': 'pio-mainnet-1', - 'ixo': 'ixo-5', - 'migaloo': 'migaloo-1', - 'teritori': 'teritori-1', - 'haqq': 'haqq_11235-1', - 'celestia': 'celestia', - 'ojo': 'agamotto', - 'chihuahua': 'chihuahua-1', - 'saga': 'ssc-1', - 'dymension': 'dymension_1100-1', - 'fxcore': 'fxcore', - 'c4e': 'perun-1', - 'bitsong': 'bitsong-2b', - 'nolus': 'pirin-1', - 'lava': 'lava-mainnet-1', - 'terra-2': 'phoenix-1', - 'terra': 'columbus-5', -}; - -module.exports = { - CHAINS: mapValues( - Object.assign( - mapValues(ethereum, reference => ({ namespace: 'eip155', reference, account: ethers.Wallet.createRandom().address })), - mapValues(cosmos, reference => ({ namespace: 'cosmos', reference, account: ethers.encodeBase58(ethers.randomBytes(32)) })), - ), - entry => Object.assign(entry, { - caip2: format(entry.namespace, entry.reference), - caip10: format(entry.namespace, entry.reference, entry.account), - }), - ), - format, -}; diff --git a/test/crosschain/utils/CAIP.test.js b/test/crosschain/utils/CAIP.test.js deleted file mode 100644 index b1da211d..00000000 --- a/test/crosschain/utils/CAIP.test.js +++ /dev/null @@ -1,54 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { CHAINS, format } = require('../chains'); - -async function fixture() { - const caip2 = await ethers.deployContract('$CAIP2'); - const caip10 = await ethers.deployContract('$CAIP10'); - const { chainId } = await ethers.provider.getNetwork(); - return { caip2, caip10, chainId }; -} - -describe('CAIP utilities', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('CAIP-2', function () { - it('local()', async function () { - expect(await this.caip2.$local()).to.equal(format('eip155', this.chainId)); - }); - - for (const { namespace, reference, caip2 } of Object.values(CHAINS)) - it (`format(${namespace}, ${reference})`, async function () { - expect(await this.caip2.$format(namespace, reference)).to.equal(caip2); - }); - - for (const { namespace, reference, caip2 } of Object.values(CHAINS)) - it(`parse(${caip2})`, async function () { - expect(await this.caip2.$parse(caip2)).to.deep.equal([ namespace, reference ]); - }); - }); - - describe('CAIP-10', function () { - const { address: account } = ethers.Wallet.createRandom(); - - it(`local(${account})`, async function () { - // lowercase encoding for now - expect(await this.caip10.$local(ethers.Typed.address(account))).to.equal(format('eip155', this.chainId, account)); - }); - - for (const { account, caip2, caip10 } of Object.values(CHAINS)) - it (`format(${caip2}, ${account})`, async function () { - expect(await this.caip10.$format(ethers.Typed.string(caip2), ethers.Typed.string(account))).to.equal(caip10); - }); - - for (const { account, caip2, caip10 } of Object.values(CHAINS)) - it(`parse(${caip10})`, async function () { - expect(await this.caip10.$parse(caip10)).to.deep.equal([ caip2, account ]); - }); - }); - -}); From 426c3fc9b653d11f56f43abb26e031efdd5ba919 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 22 Oct 2024 18:14:51 +0200 Subject: [PATCH 41/51] add/move files that are no longer planned to be in the main repo --- .../axelar/AxelarGatewayDestination.sol | 3 +- .../crosschain/axelar/AxelarGatewaySource.sol | 3 +- .../{ => axelar}/mocks/AxelarGatewayMock.sol | 0 .../{vendor => interfaces}/draft-IERC7786.sol | 0 .../crosschain/mocks/ERC7786GatewayMock.sol | 65 +++++++++++ .../crosschain/mocks/ERC7786ReceiverMock.sol | 12 +- .../draft-ERC7786Receiver.sol | 2 +- test/crosschain/ERC7786Receiver.test.js | 85 ++++++++++++++ test/helpers/chains.js | 109 ++++++++++++++++++ test/helpers/iterate.js | 36 ++++++ test/helpers/random.js | 19 +++ 11 files changed, 323 insertions(+), 11 deletions(-) rename contracts/crosschain/{ => axelar}/mocks/AxelarGatewayMock.sol (100%) rename contracts/crosschain/{vendor => interfaces}/draft-IERC7786.sol (100%) create mode 100644 contracts/crosschain/mocks/ERC7786GatewayMock.sol rename contracts/crosschain/{vendor => utils}/draft-ERC7786Receiver.sol (98%) create mode 100644 test/crosschain/ERC7786Receiver.test.js create mode 100644 test/helpers/chains.js create mode 100644 test/helpers/iterate.js create mode 100644 test/helpers/random.js diff --git a/contracts/crosschain/axelar/AxelarGatewayDestination.sol b/contracts/crosschain/axelar/AxelarGatewayDestination.sol index fb979b73..f777af8c 100644 --- a/contracts/crosschain/axelar/AxelarGatewayDestination.sol +++ b/contracts/crosschain/axelar/AxelarGatewayDestination.sol @@ -6,9 +6,8 @@ import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contract import {CAIP2} from "@openzeppelin/contracts@master/utils/CAIP2.sol"; import {CAIP10} from "@openzeppelin/contracts@master/utils/CAIP10.sol"; import {Strings} from "@openzeppelin/contracts@master/utils/Strings.sol"; +import {IERC7786GatewayDestinationPassive, IERC7786Receiver} from "../interfaces/draft-IERC7786.sol"; import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; -// import {IERC7786GatewayDestinationPassive, IERC7786Receiver} from "@openzeppelin/contracts@master/interfaces/IERC7786.sol"; -import {IERC7786GatewayDestinationPassive, IERC7786Receiver} from "../vendor/draft-IERC7786.sol"; abstract contract AxelarGatewayDestination is IERC7786GatewayDestinationPassive, AxelarGatewayBase, AxelarExecutable { using Strings for address; diff --git a/contracts/crosschain/axelar/AxelarGatewaySource.sol b/contracts/crosschain/axelar/AxelarGatewaySource.sol index 1e81b09c..4b94ca98 100644 --- a/contracts/crosschain/axelar/AxelarGatewaySource.sol +++ b/contracts/crosschain/axelar/AxelarGatewaySource.sol @@ -6,8 +6,7 @@ import {CAIP2} from "@openzeppelin/contracts@master/utils/CAIP2.sol"; import {CAIP10} from "@openzeppelin/contracts@master/utils/CAIP10.sol"; import {Strings} from "@openzeppelin/contracts@master/utils/Strings.sol"; import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; -// import {IERC7786GatewaySource} from "@openzeppelin/contracts@master/interfaces/IERC7786.sol"; -import {IERC7786GatewaySource} from "../vendor/draft-IERC7786.sol"; +import {IERC7786GatewaySource} from "../interfaces/draft-IERC7786.sol"; abstract contract AxelarGatewaySource is IERC7786GatewaySource, AxelarGatewayBase { using Strings for address; diff --git a/contracts/crosschain/mocks/AxelarGatewayMock.sol b/contracts/crosschain/axelar/mocks/AxelarGatewayMock.sol similarity index 100% rename from contracts/crosschain/mocks/AxelarGatewayMock.sol rename to contracts/crosschain/axelar/mocks/AxelarGatewayMock.sol diff --git a/contracts/crosschain/vendor/draft-IERC7786.sol b/contracts/crosschain/interfaces/draft-IERC7786.sol similarity index 100% rename from contracts/crosschain/vendor/draft-IERC7786.sol rename to contracts/crosschain/interfaces/draft-IERC7786.sol diff --git a/contracts/crosschain/mocks/ERC7786GatewayMock.sol b/contracts/crosschain/mocks/ERC7786GatewayMock.sol new file mode 100644 index 00000000..e1a1214a --- /dev/null +++ b/contracts/crosschain/mocks/ERC7786GatewayMock.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +import {BitMaps} from "@openzeppelin/contracts@master/utils/structs/BitMaps.sol"; +import {Strings} from "@openzeppelin/contracts@master/utils/Strings.sol"; +import {CAIP2} from "@openzeppelin/contracts@master/utils/CAIP2.sol"; +import {CAIP10} from "@openzeppelin/contracts@master/utils/CAIP10.sol"; +import {IERC7786GatewaySource, IERC7786GatewayDestinationPassive, IERC7786Receiver} from "../interfaces/draft-IERC7786.sol"; + +contract ERC7786GatewayMock is IERC7786GatewaySource, IERC7786GatewayDestinationPassive { + using BitMaps for BitMaps.BitMap; + using Strings for *; + + BitMaps.BitMap private _outbox; + bool private _activeMode; + + function _setActive(bool newActiveMode) internal { + _activeMode = newActiveMode; + } + + function supportsAttribute(bytes4 /*selector*/) public pure returns (bool) { + return false; + } + + function sendMessage( + string calldata destination, // CAIP-2 chain ID + string calldata receiver, // CAIP-10 account ID + bytes calldata payload, + bytes[] calldata attributes + ) public payable returns (bytes32) { + string memory source = CAIP2.local(); + string memory sender = msg.sender.toChecksumHexString(); + + require(destination.equal(source), "This mock only supports local messages"); + for (uint256 i = 0; i < attributes.length; ++i) { + bytes4 selector = bytes4(attributes[i][0:4]); + if (!supportsAttribute(selector)) revert UnsupportedAttribute(selector); + } + + if (_activeMode) { + address target = Strings.parseAddress(receiver); + IERC7786Receiver(target).receiveMessage(address(this), new bytes(0), source, sender, payload, attributes); + } else { + _outbox.set(uint256(keccak256(abi.encode(source, sender, receiver, payload, attributes)))); + } + + emit MessageCreated(0, CAIP10.format(source, sender), CAIP10.format(source, receiver), payload, attributes); + return 0; + } + + function validateReceivedMessage( + bytes calldata /*messageKey*/, // this mock doesn't use a messageKey + string calldata source, + string calldata sender, + bytes calldata payload, + bytes[] calldata attributes + ) public { + uint256 digest = uint256( + keccak256(abi.encode(source, sender, msg.sender.toChecksumHexString(), payload, attributes)) + ); + require(_outbox.get(digest), "invalid message"); + _outbox.unset(digest); + } +} diff --git a/contracts/crosschain/mocks/ERC7786ReceiverMock.sol b/contracts/crosschain/mocks/ERC7786ReceiverMock.sol index e6356fc2..47f5bbd9 100644 --- a/contracts/crosschain/mocks/ERC7786ReceiverMock.sol +++ b/contracts/crosschain/mocks/ERC7786ReceiverMock.sol @@ -1,20 +1,20 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.27; +pragma solidity ^0.8.0; -import {ERC7786Receiver} from "../vendor/draft-ERC7786Receiver.sol"; +import {ERC7786Receiver} from "../utils/draft-ERC7786Receiver.sol"; contract ERC7786ReceiverMock is ERC7786Receiver { - address public immutable GATEWAY; + address private immutable _gateway; event MessageReceived(address gateway, string source, string sender, bytes payload, bytes[] attributes); - constructor(address _gateway) { - GATEWAY = _gateway; + constructor(address gateway_) { + _gateway = gateway_; } function _isKnownGateway(address instance) internal view virtual override returns (bool) { - return instance == GATEWAY; + return instance == _gateway; } function _processMessage( diff --git a/contracts/crosschain/vendor/draft-ERC7786Receiver.sol b/contracts/crosschain/utils/draft-ERC7786Receiver.sol similarity index 98% rename from contracts/crosschain/vendor/draft-ERC7786Receiver.sol rename to contracts/crosschain/utils/draft-ERC7786Receiver.sol index 33c2dea1..454ee023 100644 --- a/contracts/crosschain/vendor/draft-ERC7786Receiver.sol +++ b/contracts/crosschain/utils/draft-ERC7786Receiver.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -import {IERC7786GatewayDestinationPassive, IERC7786Receiver} from "./draft-IERC7786.sol"; +import {IERC7786GatewayDestinationPassive, IERC7786Receiver} from "../interfaces/draft-IERC7786.sol"; /** * @dev Base implementation of an ERC-7786 compliant cross-chain message receiver. diff --git a/test/crosschain/ERC7786Receiver.test.js b/test/crosschain/ERC7786Receiver.test.js new file mode 100644 index 00000000..cc8547d4 --- /dev/null +++ b/test/crosschain/ERC7786Receiver.test.js @@ -0,0 +1,85 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + +const { getLocalCAIP } = require('../helpers/chains'); +const payload = require('../helpers/random').generators.hexBytes(128); +const attributes = []; + +const getAddress = account => ethers.getAddress(account.target ?? account.address ?? account); + +async function fixture() { + const [sender, notAGateway] = await ethers.getSigners(); + const { caip2, toCaip10 } = await getLocalCAIP(); + + const gateway = await ethers.deployContract('$ERC7786GatewayMock'); + const receiver = await ethers.deployContract('$ERC7786ReceiverMock', [gateway]); + + return { sender, notAGateway, gateway, receiver, caip2, toCaip10 }; +} + +// NOTE: here we are only testing the receiver. Failures of the gateway itself (invalid attributes, ...) are out of scope. +describe('ERC7786Receiver', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('active mode', function () { + beforeEach(async function () { + await this.gateway.$_setActive(true); + }); + + it('nominal workflow', async function () { + await expect(this.gateway.connect(this.sender).sendMessage(this.caip2, getAddress(this.receiver), payload, attributes)) + .to.emit(this.gateway, 'MessageCreated') + .withArgs(ethers.ZeroHash, this.toCaip10(this.sender), this.toCaip10(this.receiver), payload, attributes) + .to.emit(this.receiver, 'MessageReceived') + .withArgs(this.gateway, this.caip2, getAddress(this.sender), payload, attributes); + }); + }); + + describe('passive mode', function () { + beforeEach(async function () { + await this.gateway.$_setActive(false); + }); + + it('nominal workflow', async function () { + await expect(this.gateway.connect(this.sender).sendMessage(this.caip2, this.receiver.target, payload, attributes)) + .to.emit(this.gateway, 'MessageCreated') + .withArgs(ethers.ZeroHash, this.toCaip10(this.sender), this.toCaip10(this.receiver), payload, attributes) + .to.not.emit(this.receiver, 'MessageReceived'); + + await expect( + this.receiver.receiveMessage(this.gateway, '0x', this.caip2, getAddress(this.sender), payload, attributes), + ) + .to.emit(this.receiver, 'MessageReceived') + .withArgs(this.gateway, this.caip2, getAddress(this.sender), payload, attributes); + }); + + it('invalid message', async function () { + await this.gateway.connect(this.sender).sendMessage(this.caip2, this.receiver.target, payload, attributes); + + // Altering the message (in this case, changing the sender's address) + // Here the error is actually triggered by the gateway itself. + await expect( + this.receiver.receiveMessage(this.gateway, '0x', this.caip2, getAddress(this.notAGateway), payload, attributes), + ).to.be.revertedWith('invalid message'); + }); + + it('invalid gateway', async function () { + await expect( + this.receiver.receiveMessage(this.notAGateway, '0x', this.caip2, getAddress(this.sender), payload, attributes), + ) + .to.be.revertedWithCustomError(this.receiver, 'ERC7786ReceiverInvalidGateway') + .withArgs(this.notAGateway); + }); + + it('with value', async function () { + await expect( + this.receiver.receiveMessage(this.gateway, '0x', this.caip2, getAddress(this.sender), payload, attributes, { + value: 1n, + }), + ).to.be.revertedWithCustomError(this.receiver, 'ERC7786ReceivePassiveModeValue'); + }); + }); +}); diff --git a/test/helpers/chains.js b/test/helpers/chains.js new file mode 100644 index 00000000..3711a812 --- /dev/null +++ b/test/helpers/chains.js @@ -0,0 +1,109 @@ +// NOTE: this file defines some examples of CAIP-2 and CAIP-10 identifiers. +// The following listing does not pretend to be exhaustive or even accurate. It SHOULD NOT be used in production. + +const { ethers } = require('hardhat'); +const { mapValues } = require('./iterate'); + +// EVM (https://axelarscan.io/resources/chains?type=evm) +const ethereum = { + Ethereum: '1', + optimism: '10', + binance: '56', + Polygon: '137', + Fantom: '250', + fraxtal: '252', + filecoin: '314', + Moonbeam: '1284', + centrifuge: '2031', + kava: '2222', + mantle: '5000', + base: '8453', + immutable: '13371', + arbitrum: '42161', + celo: '42220', + Avalanche: '43114', + linea: '59144', + blast: '81457', + scroll: '534352', + aurora: '1313161554', +}; + +// Cosmos (https://axelarscan.io/resources/chains?type=cosmos) +const cosmos = { + Axelarnet: 'axelar-dojo-1', + osmosis: 'osmosis-1', + cosmoshub: 'cosmoshub-4', + juno: 'juno-1', + 'e-money': 'emoney-3', + injective: 'injective-1', + crescent: 'crescent-1', + kujira: 'kaiyo-1', + 'secret-snip': 'secret-4', + secret: 'secret-4', + sei: 'pacific-1', + stargaze: 'stargaze-1', + assetmantle: 'mantle-1', + fetch: 'fetchhub-4', + ki: 'kichain-2', + evmos: 'evmos_9001-2', + aura: 'xstaxy-1', + comdex: 'comdex-1', + persistence: 'core-1', + regen: 'regen-1', + umee: 'umee-1', + agoric: 'agoric-3', + xpla: 'dimension_37-1', + acre: 'acre_9052-1', + stride: 'stride-1', + carbon: 'carbon-1', + sommelier: 'sommelier-3', + neutron: 'neutron-1', + rebus: 'reb_1111-1', + archway: 'archway-1', + provenance: 'pio-mainnet-1', + ixo: 'ixo-5', + migaloo: 'migaloo-1', + teritori: 'teritori-1', + haqq: 'haqq_11235-1', + celestia: 'celestia', + ojo: 'agamotto', + chihuahua: 'chihuahua-1', + saga: 'ssc-1', + dymension: 'dymension_1100-1', + fxcore: 'fxcore', + c4e: 'perun-1', + bitsong: 'bitsong-2b', + nolus: 'pirin-1', + lava: 'lava-mainnet-1', + 'terra-2': 'phoenix-1', + terra: 'columbus-5', +}; + +const makeCAIP = ({ namespace, reference, account }) => ({ + namespace, + reference, + account, + caip2: `${namespace}:${reference}`, + caip10: `${namespace}:${reference}:${account}`, + toCaip10: other => `${namespace}:${reference}:${ethers.getAddress(other.target ?? other.address ?? other)}`, +}); + +module.exports = { + CHAINS: mapValues( + Object.assign( + mapValues(ethereum, reference => ({ + namespace: 'eip155', + reference, + account: ethers.Wallet.createRandom().address, + })), + mapValues(cosmos, reference => ({ + namespace: 'cosmos', + reference, + account: ethers.encodeBase58(ethers.randomBytes(32)), + })), + ), + makeCAIP, + ), + getLocalCAIP: account => + ethers.provider.getNetwork().then(({ chainId }) => makeCAIP({ namespace: 'eip155', reference: chainId, account })), +}; diff --git a/test/helpers/iterate.js b/test/helpers/iterate.js new file mode 100644 index 00000000..c7403d52 --- /dev/null +++ b/test/helpers/iterate.js @@ -0,0 +1,36 @@ +module.exports = { + // ================================================= Array helpers ================================================= + + // Cut an array into an array of sized-length arrays + // Example: chunk([1,2,3,4,5,6,7,8], 3) → [[1,2,3],[4,5,6],[7,8]] + chunk: (array, size = 1) => + Array.from({ length: Math.ceil(array.length / size) }, (_, i) => array.slice(i * size, i * size + size)), + + // Cartesian cross product of an array of arrays + // Example: product([1,2],[a,b,c],[true]) → [[1,a,true],[1,b,true],[1,c,true],[2,a,true],[2,b,true],[2,c,true]] + product: (...arrays) => arrays.reduce((a, b) => a.flatMap(ai => b.map(bi => [...ai, bi])), [[]]), + + // Range from start to end in increment + // Example: range(17,42,7) → [17,24,31,38] + range: (start, stop = undefined, step = 1) => { + if (stop == undefined) { + stop = start; + start = 0; + } + return start < stop ? Array.from({ length: (stop - start + step - 1) / step }, (_, i) => start + i * step) : []; + }, + + // Unique elements, with an optional getter function + // Example: unique([1,1,2,3,4,8,1,3,8,13,42]) → [1,2,3,4,8,13,42] + unique: (array, op = x => x) => array.filter((obj, i) => array.findIndex(entry => op(obj) === op(entry)) === i), + + // Zip arrays together. If some arrays are smaller, undefined is used as a filler. + // Example: zip([1,2],[a,b,c],[true]) → [[1,a,true],[2,b,undefined],[undefined,c,undefined]] + zip: (...args) => Array.from({ length: Math.max(...args.map(arg => arg.length)) }, (_, i) => args.map(arg => arg[i])), + + // ================================================ Object helpers ================================================= + + // Create a new object by mapping the values through a function, keeping the keys + // Example: mapValues({a:1,b:2,c:3}, x => x**2) → {a:1,b:4,c:9} + mapValues: (obj, fn) => Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)])), +}; diff --git a/test/helpers/random.js b/test/helpers/random.js new file mode 100644 index 00000000..3adeed0a --- /dev/null +++ b/test/helpers/random.js @@ -0,0 +1,19 @@ +const { ethers } = require('hardhat'); + +const generators = { + address: () => ethers.Wallet.createRandom().address, + bytes32: () => ethers.hexlify(ethers.randomBytes(32)), + uint256: () => ethers.toBigInt(ethers.randomBytes(32)), + int256: () => ethers.toBigInt(ethers.randomBytes(32)) + ethers.MinInt256, + hexBytes: length => ethers.hexlify(ethers.randomBytes(length)), +}; + +generators.address.zero = ethers.ZeroAddress; +generators.bytes32.zero = ethers.ZeroHash; +generators.uint256.zero = 0n; +generators.int256.zero = 0n; +generators.hexBytes.zero = '0x'; + +module.exports = { + generators, +}; From 12236ddb500230ccc5ce64bf9d8414ce5febf24d Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 22 Oct 2024 18:21:17 +0200 Subject: [PATCH 42/51] get submodules when running tests --- .github/workflows/checks.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 72b9df65..1728536d 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -33,6 +33,8 @@ jobs: GAS: true steps: - uses: actions/checkout@v4 + with: + submodules: recursive - name: Set up environment uses: ./.github/actions/setup - name: Run tests and generate gas report @@ -42,6 +44,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: recursive - name: Set up environment uses: ./.github/actions/setup - name: Run coverage @@ -62,6 +66,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: recursive - name: Set up environment uses: ./.github/actions/setup - run: rm foundry.toml From aad874585b768a3bfeab3236a1931ef01c5b7d64 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 22 Oct 2024 18:22:06 +0200 Subject: [PATCH 43/51] Apply suggestions from code review --- contracts/crosschain/axelar/AxelarGatewayBase.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/crosschain/axelar/AxelarGatewayBase.sol b/contracts/crosschain/axelar/AxelarGatewayBase.sol index 684c4655..37b493e0 100644 --- a/contracts/crosschain/axelar/AxelarGatewayBase.sol +++ b/contracts/crosschain/axelar/AxelarGatewayBase.sol @@ -13,7 +13,6 @@ abstract contract AxelarGatewayBase is Ownable { IAxelarGateway public immutable localGateway; - // TODO: merge these two (and the corresponding setters?) mapping(string caip2 => string remoteGateway) private _remoteGateways; mapping(string caip2OrAxelar => string axelarOfCaip2) private _chainEquivalence; From 2aa5c15fffee617e9c2c59591216bf8231f9f518 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 22 Oct 2024 18:22:27 +0200 Subject: [PATCH 44/51] Apply suggestions from code review Co-authored-by: Francisco Giordano --- contracts/crosschain/axelar/AxelarGatewaySource.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/crosschain/axelar/AxelarGatewaySource.sol b/contracts/crosschain/axelar/AxelarGatewaySource.sol index 4b94ca98..2a78e9fe 100644 --- a/contracts/crosschain/axelar/AxelarGatewaySource.sol +++ b/contracts/crosschain/axelar/AxelarGatewaySource.sol @@ -21,7 +21,6 @@ abstract contract AxelarGatewaySource is IERC7786GatewaySource, AxelarGatewayBas bytes calldata payload, bytes[] calldata attributes ) external payable virtual returns (bytes32) { - // TODO: add support for value and attributes ? require(msg.value == 0, "Value not supported"); for (uint256 i = 0; i < attributes.length; ++i) { bytes4 selector = bytes4(attributes[i][0:4]); From 98dede15c5c00507b1b74d34ea0ddb0750721333 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 22 Oct 2024 18:24:30 +0200 Subject: [PATCH 45/51] simplify --- contracts/crosschain/axelar/AxelarGatewaySource.sol | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/contracts/crosschain/axelar/AxelarGatewaySource.sol b/contracts/crosschain/axelar/AxelarGatewaySource.sol index 2a78e9fe..b358aed5 100644 --- a/contracts/crosschain/axelar/AxelarGatewaySource.sol +++ b/contracts/crosschain/axelar/AxelarGatewaySource.sol @@ -22,10 +22,7 @@ abstract contract AxelarGatewaySource is IERC7786GatewaySource, AxelarGatewayBas bytes[] calldata attributes ) external payable virtual returns (bytes32) { require(msg.value == 0, "Value not supported"); - for (uint256 i = 0; i < attributes.length; ++i) { - bytes4 selector = bytes4(attributes[i][0:4]); - require(supportsAttribute(selector), UnsupportedAttribute(selector)); - } + require(attributes.length == 0, UnsupportedAttribute(bytes4(attributes[0][0:4]))); // Create the package string memory sender = msg.sender.toChecksumHexString(); From 9bd6a3b34146d9037c9200e891f772ea4ae41387 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 22 Oct 2024 18:29:07 +0200 Subject: [PATCH 46/51] update --- .../crosschain/axelar/AxelarGatewayDestination.sol | 4 ++-- contracts/crosschain/axelar/AxelarGatewaySource.sol | 6 +++--- contracts/crosschain/mocks/ERC7786GatewayMock.sol | 12 +++++------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/contracts/crosschain/axelar/AxelarGatewayDestination.sol b/contracts/crosschain/axelar/AxelarGatewayDestination.sol index f777af8c..c7f145b1 100644 --- a/contracts/crosschain/axelar/AxelarGatewayDestination.sol +++ b/contracts/crosschain/axelar/AxelarGatewayDestination.sol @@ -20,7 +20,7 @@ abstract contract AxelarGatewayDestination is IERC7786GatewayDestinationPassive, string calldata sender, // CAIP-10 bytes calldata payload, bytes[] calldata attributes - ) external virtual { + ) external { // Extract Axelar commandId bytes32 commandId = abi.decode(messageKey, (bytes32)); @@ -56,7 +56,7 @@ abstract contract AxelarGatewayDestination is IERC7786GatewayDestinationPassive, string calldata remoteChain, // chain of the remote gateway - axelar format string calldata remoteAccount, // address of the remote gateway bytes calldata adapterPayload - ) internal virtual override { + ) internal override { // Parse the package (string memory sender, string memory receiver, bytes memory payload, bytes[] memory attributes) = abi.decode( adapterPayload, diff --git a/contracts/crosschain/axelar/AxelarGatewaySource.sol b/contracts/crosschain/axelar/AxelarGatewaySource.sol index b358aed5..4ae4000d 100644 --- a/contracts/crosschain/axelar/AxelarGatewaySource.sol +++ b/contracts/crosschain/axelar/AxelarGatewaySource.sol @@ -11,7 +11,7 @@ import {IERC7786GatewaySource} from "../interfaces/draft-IERC7786.sol"; abstract contract AxelarGatewaySource is IERC7786GatewaySource, AxelarGatewayBase { using Strings for address; - function supportsAttribute(bytes4 /*selector*/) public view virtual returns (bool) { + function supportsAttribute(bytes4 /*selector*/) public pure returns (bool) { return false; } @@ -20,9 +20,9 @@ abstract contract AxelarGatewaySource is IERC7786GatewaySource, AxelarGatewayBas string calldata receiver, // i.e. address bytes calldata payload, bytes[] calldata attributes - ) external payable virtual returns (bytes32) { + ) external payable returns (bytes32) { require(msg.value == 0, "Value not supported"); - require(attributes.length == 0, UnsupportedAttribute(bytes4(attributes[0][0:4]))); + if (attributes.length > 0) revert UnsupportedAttribute(bytes4(attributes[0][0:4])); // Create the package string memory sender = msg.sender.toChecksumHexString(); diff --git a/contracts/crosschain/mocks/ERC7786GatewayMock.sol b/contracts/crosschain/mocks/ERC7786GatewayMock.sol index e1a1214a..258bca1d 100644 --- a/contracts/crosschain/mocks/ERC7786GatewayMock.sol +++ b/contracts/crosschain/mocks/ERC7786GatewayMock.sol @@ -29,14 +29,12 @@ contract ERC7786GatewayMock is IERC7786GatewaySource, IERC7786GatewayDestination bytes calldata payload, bytes[] calldata attributes ) public payable returns (bytes32) { - string memory source = CAIP2.local(); - string memory sender = msg.sender.toChecksumHexString(); + require(msg.value == 0, "Value not supported"); + if (attributes.length > 0) revert UnsupportedAttribute(bytes4(attributes[0][0:4])); + require(destination.equal(CAIP2.local()), "This mock only supports local messages"); - require(destination.equal(source), "This mock only supports local messages"); - for (uint256 i = 0; i < attributes.length; ++i) { - bytes4 selector = bytes4(attributes[i][0:4]); - if (!supportsAttribute(selector)) revert UnsupportedAttribute(selector); - } + string memory source = destination; + string memory sender = msg.sender.toChecksumHexString(); if (_activeMode) { address target = Strings.parseAddress(receiver); From c926b36c6b514638ba51aab7ee83b644ec71379f Mon Sep 17 00:00:00 2001 From: ernestognw Date: Tue, 29 Oct 2024 22:40:18 +0700 Subject: [PATCH 47/51] validateReceivedMessage -> setExecutedMessage --- contracts/crosschain/axelar/AxelarGatewayDestination.sol | 2 +- contracts/crosschain/interfaces/draft-IERC7786.sol | 2 +- contracts/crosschain/mocks/ERC7786GatewayMock.sol | 2 +- contracts/crosschain/utils/draft-ERC7786Receiver.sol | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/crosschain/axelar/AxelarGatewayDestination.sol b/contracts/crosschain/axelar/AxelarGatewayDestination.sol index c7f145b1..91f24a8f 100644 --- a/contracts/crosschain/axelar/AxelarGatewayDestination.sol +++ b/contracts/crosschain/axelar/AxelarGatewayDestination.sol @@ -14,7 +14,7 @@ abstract contract AxelarGatewayDestination is IERC7786GatewayDestinationPassive, using Strings for string; /// @dev Passive mode - function validateReceivedMessage( + function setExecutedMessage( bytes calldata messageKey, string calldata source, // CAIP-2 string calldata sender, // CAIP-10 diff --git a/contracts/crosschain/interfaces/draft-IERC7786.sol b/contracts/crosschain/interfaces/draft-IERC7786.sol index d3c617c4..d513f92e 100644 --- a/contracts/crosschain/interfaces/draft-IERC7786.sol +++ b/contracts/crosschain/interfaces/draft-IERC7786.sol @@ -68,7 +68,7 @@ interface IERC7786GatewayDestinationPassive { * * NOTE: implementing this interface is OPTIONAL. Some destination gateway MAY only support active mode. */ - function validateReceivedMessage( + function setExecutedMessage( bytes calldata messageKey, string calldata source, string calldata sender, diff --git a/contracts/crosschain/mocks/ERC7786GatewayMock.sol b/contracts/crosschain/mocks/ERC7786GatewayMock.sol index 258bca1d..df370d41 100644 --- a/contracts/crosschain/mocks/ERC7786GatewayMock.sol +++ b/contracts/crosschain/mocks/ERC7786GatewayMock.sol @@ -47,7 +47,7 @@ contract ERC7786GatewayMock is IERC7786GatewaySource, IERC7786GatewayDestination return 0; } - function validateReceivedMessage( + function setExecutedMessage( bytes calldata /*messageKey*/, // this mock doesn't use a messageKey string calldata source, string calldata sender, diff --git a/contracts/crosschain/utils/draft-ERC7786Receiver.sol b/contracts/crosschain/utils/draft-ERC7786Receiver.sol index 454ee023..329e3d81 100644 --- a/contracts/crosschain/utils/draft-ERC7786Receiver.sol +++ b/contracts/crosschain/utils/draft-ERC7786Receiver.sol @@ -35,7 +35,7 @@ abstract contract ERC7786Receiver is IERC7786Receiver { } else if (_isKnownGateway(gateway)) { // Passive mode if (msg.value != 0) revert ERC7786ReceivePassiveModeValue(); - IERC7786GatewayDestinationPassive(gateway).validateReceivedMessage( + IERC7786GatewayDestinationPassive(gateway).setExecutedMessage( gatewayMessageKey, source, sender, From a5f1368e75d61458c446d8b2d0bf612b7e5e3150 Mon Sep 17 00:00:00 2001 From: ernestognw Date: Tue, 29 Oct 2024 23:58:37 +0700 Subject: [PATCH 48/51] Add docs --- .../crosschain/axelar/AxelarGatewayBase.sol | 19 +++++++++++- .../axelar/AxelarGatewayDestination.sol | 29 +++++++++++++------ .../crosschain/axelar/AxelarGatewaySource.sol | 8 +++++ 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/contracts/crosschain/axelar/AxelarGatewayBase.sol b/contracts/crosschain/axelar/AxelarGatewayBase.sol index 37b493e0..eca23f60 100644 --- a/contracts/crosschain/axelar/AxelarGatewayBase.sol +++ b/contracts/crosschain/axelar/AxelarGatewayBase.sol @@ -5,31 +5,47 @@ pragma solidity ^0.8.0; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol"; +/** + * @dev Base implementation of a cross-chain gateway adapter for the Axelar Network. + * + * This contract allows developers to register equivalence between chains (i.e. CAIP-2 chain identifiers + * to Axelar chain identifiers) and remote gateways (i.e. gateways on other chains) to + * facilitate cross-chain communication. + */ abstract contract AxelarGatewayBase is Ownable { + /// @dev A remote gateway has been registered for a chain. event RegisteredRemoteGateway(string caip2, string gatewayAddress); + + /// @dev A chain equivalence has been registered. event RegisteredChainEquivalence(string caip2, string destinationChain); + /// @dev Error emitted when an unsupported chain is queried. error UnsupportedChain(string caip2); + /// @dev Axelar's official gateway for the current chain. IAxelarGateway public immutable localGateway; mapping(string caip2 => string remoteGateway) private _remoteGateways; - mapping(string caip2OrAxelar => string axelarOfCaip2) private _chainEquivalence; + mapping(string caip2OrAxelar => string axelarOrCaip2) private _chainEquivalence; + /// @dev Sets the local gateway address (i.e. Axelar's official gateway for the current chain). constructor(IAxelarGateway _gateway) { localGateway = _gateway; } + /// @dev Returns the equivalent chain given an id that can be either CAIP-2 or an Axelar network identifier. function getEquivalentChain(string memory input) public view virtual returns (string memory output) { output = _chainEquivalence[input]; require(bytes(output).length > 0, UnsupportedChain(input)); } + /// @dev Returns the remote gateway address for a given chain. function getRemoteGateway(string memory caip2) public view virtual returns (string memory remoteGateway) { remoteGateway = _remoteGateways[caip2]; require(bytes(remoteGateway).length > 0, UnsupportedChain(caip2)); } + /// @dev Registers a chain equivalence between a CAIP-2 chain identifier and an Axelar network identifier. function registerChainEquivalence(string calldata caip2, string calldata axelarSupported) public virtual onlyOwner { require(bytes(_chainEquivalence[caip2]).length == 0); _chainEquivalence[caip2] = axelarSupported; @@ -37,6 +53,7 @@ abstract contract AxelarGatewayBase is Ownable { emit RegisteredChainEquivalence(caip2, axelarSupported); } + /// @dev Registers a remote gateway address for a given CAIP-2 chain identifier. function registerRemoteGateway(string calldata caip2, string calldata remoteGateway) public virtual onlyOwner { require(bytes(_remoteGateways[caip2]).length == 0); _remoteGateways[caip2] = remoteGateway; diff --git a/contracts/crosschain/axelar/AxelarGatewayDestination.sol b/contracts/crosschain/axelar/AxelarGatewayDestination.sol index 91f24a8f..4dc6f9f4 100644 --- a/contracts/crosschain/axelar/AxelarGatewayDestination.sol +++ b/contracts/crosschain/axelar/AxelarGatewayDestination.sol @@ -9,11 +9,18 @@ import {Strings} from "@openzeppelin/contracts@master/utils/Strings.sol"; import {IERC7786GatewayDestinationPassive, IERC7786Receiver} from "../interfaces/draft-IERC7786.sol"; import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; +/** + * @dev Implementation of an ERC7786 gateway destination adapter for the Axelar Network in dual mode. + * + * The contract provides an internal {_execute} function to be used by a child contract (active mode), + * Alternatively, it provides a way to set a message as executed by calling the {setExecutedMessage} + * function (passive mode). + */ abstract contract AxelarGatewayDestination is IERC7786GatewayDestinationPassive, AxelarGatewayBase, AxelarExecutable { using Strings for address; using Strings for string; - /// @dev Passive mode + /// @dev Sets a message as executed so it can't be executed again. Should be called by the receiver contract. function setExecutedMessage( bytes calldata messageKey, string calldata source, // CAIP-2 @@ -44,14 +51,18 @@ abstract contract AxelarGatewayDestination is IERC7786GatewayDestinationPassive, ); } - /// @dev Active mode - // In this function: - // - `remoteChain` is in the Axelar format. It should not be expected to be a proper CAIP-2 format - // - `remoteAccount` is the sender of the crosschain message. That should be the remote gateway on the chain which - // the message originates from. It is NOT the sender of the crosschain message - // - // Proper CAIP-10 encoding of the message sender (including the CAIP-2 name of the origin chain can be found in - // the message) + /** + * @dev Active mode execution of a cross-chain message. + * + * In this function: + * + * - `remoteChain` is in the Axelar format. It should not be expected to be a proper CAIP-2 format + * - `remoteAccount` is the sender of the crosschain message. That should be the remote gateway on the chain which + * the message originates from. It is NOT the sender of the crosschain message + * + * Proper CAIP-10 encoding of the message sender (including the CAIP-2 name of the origin chain can be found in + * the message) + */ function _execute( string calldata remoteChain, // chain of the remote gateway - axelar format string calldata remoteAccount, // address of the remote gateway diff --git a/contracts/crosschain/axelar/AxelarGatewaySource.sol b/contracts/crosschain/axelar/AxelarGatewaySource.sol index 4ae4000d..68d5b905 100644 --- a/contracts/crosschain/axelar/AxelarGatewaySource.sol +++ b/contracts/crosschain/axelar/AxelarGatewaySource.sol @@ -8,13 +8,21 @@ import {Strings} from "@openzeppelin/contracts@master/utils/Strings.sol"; import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; import {IERC7786GatewaySource} from "../interfaces/draft-IERC7786.sol"; +/** + * @dev Implementation of an ERC7786 gateway source adapter for the Axelar Network. + * + * The contract provides a way to send messages to a remote chain using the Axelar Network + * using the {sendMessage} function. + */ abstract contract AxelarGatewaySource is IERC7786GatewaySource, AxelarGatewayBase { using Strings for address; + /// @inheritdoc IERC7786GatewaySource function supportsAttribute(bytes4 /*selector*/) public pure returns (bool) { return false; } + /// @inheritdoc IERC7786GatewaySource function sendMessage( string calldata destination, // CAIP-2 chain ID string calldata receiver, // i.e. address From 608990069cae8999d3a49ace02d557c4fc34a1c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Wed, 30 Oct 2024 11:31:22 +0700 Subject: [PATCH 49/51] Update AxelarGatewayDestination.sol Co-authored-by: Francisco Giordano --- contracts/crosschain/axelar/AxelarGatewayDestination.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/crosschain/axelar/AxelarGatewayDestination.sol b/contracts/crosschain/axelar/AxelarGatewayDestination.sol index 4dc6f9f4..ae53483b 100644 --- a/contracts/crosschain/axelar/AxelarGatewayDestination.sol +++ b/contracts/crosschain/axelar/AxelarGatewayDestination.sol @@ -60,8 +60,8 @@ abstract contract AxelarGatewayDestination is IERC7786GatewayDestinationPassive, * - `remoteAccount` is the sender of the crosschain message. That should be the remote gateway on the chain which * the message originates from. It is NOT the sender of the crosschain message * - * Proper CAIP-10 encoding of the message sender (including the CAIP-2 name of the origin chain can be found in - * the message) + * Proper CAIP-10 encoding of the message sender (including the CAIP-2 name of the origin chain) can be found in + * the message */ function _execute( string calldata remoteChain, // chain of the remote gateway - axelar format From a38ec99e8fc681734cab84e95a9a5dfe789cceb3 Mon Sep 17 00:00:00 2001 From: ernestognw Date: Wed, 30 Oct 2024 12:00:40 +0700 Subject: [PATCH 50/51] Update --- contracts/crosschain/axelar/AxelarGatewayDestination.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/crosschain/axelar/AxelarGatewayDestination.sol b/contracts/crosschain/axelar/AxelarGatewayDestination.sol index ae53483b..76db05e6 100644 --- a/contracts/crosschain/axelar/AxelarGatewayDestination.sol +++ b/contracts/crosschain/axelar/AxelarGatewayDestination.sol @@ -12,7 +12,7 @@ import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; /** * @dev Implementation of an ERC7786 gateway destination adapter for the Axelar Network in dual mode. * - * The contract provides an internal {_execute} function to be used by a child contract (active mode), + * The contract implements implements AxelarExecutable's {_execute} function to execute the message (active mode). * Alternatively, it provides a way to set a message as executed by calling the {setExecutedMessage} * function (passive mode). */ @@ -60,8 +60,8 @@ abstract contract AxelarGatewayDestination is IERC7786GatewayDestinationPassive, * - `remoteAccount` is the sender of the crosschain message. That should be the remote gateway on the chain which * the message originates from. It is NOT the sender of the crosschain message * - * Proper CAIP-10 encoding of the message sender (including the CAIP-2 name of the origin chain) can be found in - * the message + * Proper CAIP-10 encoding of the message sender (including the CAIP-2 name of the origin chain can be found in + * the message) */ function _execute( string calldata remoteChain, // chain of the remote gateway - axelar format From 27fbb9325c503a84568dbefc851cf48aea0f201f Mon Sep 17 00:00:00 2001 From: ernestognw Date: Wed, 30 Oct 2024 12:10:09 +0700 Subject: [PATCH 51/51] Update --- contracts/crosschain/axelar/AxelarGatewayDestination.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/crosschain/axelar/AxelarGatewayDestination.sol b/contracts/crosschain/axelar/AxelarGatewayDestination.sol index 76db05e6..fd88618e 100644 --- a/contracts/crosschain/axelar/AxelarGatewayDestination.sol +++ b/contracts/crosschain/axelar/AxelarGatewayDestination.sol @@ -12,7 +12,8 @@ import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; /** * @dev Implementation of an ERC7786 gateway destination adapter for the Axelar Network in dual mode. * - * The contract implements implements AxelarExecutable's {_execute} function to execute the message (active mode). + * The contract implements implements AxelarExecutable's {_execute} function to execute the message, + * converting Axelar's native workflow into the standard ERC7786 active mode. * Alternatively, it provides a way to set a message as executed by calling the {setExecutedMessage} * function (passive mode). */