From 4df20f54b3ca14462bec2e36f64ff2c5b5a6a1aa Mon Sep 17 00:00:00 2001 From: Alessandro Manfredi Date: Thu, 9 May 2024 14:41:55 +0200 Subject: [PATCH 1/3] feat(evm): creates debridge reporter & adapter --- .../adapters/DeBridge/DeBridgeAdapter.sol | 40 +++ .../adapters/DeBridge/DeBridgeReporter.sol | 38 +++ .../DeBridge/interfaces/ICallProxy.sol | 44 ++++ .../DeBridge/interfaces/IDeBridgeGate.sol | 242 ++++++++++++++++++ 4 files changed, 364 insertions(+) create mode 100644 packages/evm/contracts/adapters/DeBridge/DeBridgeAdapter.sol create mode 100644 packages/evm/contracts/adapters/DeBridge/DeBridgeReporter.sol create mode 100644 packages/evm/contracts/adapters/DeBridge/interfaces/ICallProxy.sol create mode 100644 packages/evm/contracts/adapters/DeBridge/interfaces/IDeBridgeGate.sol diff --git a/packages/evm/contracts/adapters/DeBridge/DeBridgeAdapter.sol b/packages/evm/contracts/adapters/DeBridge/DeBridgeAdapter.sol new file mode 100644 index 00000000..bf298ed5 --- /dev/null +++ b/packages/evm/contracts/adapters/DeBridge/DeBridgeAdapter.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.20; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { BlockHashAdapter } from "../BlockHashAdapter.sol"; +import { IDeBridgeGate } from "./interfaces/IDeBridgeGate.sol"; +import { ICallProxy } from "./interfaces/ICallProxy.sol"; + +contract DeBridgeAdapter is BlockHashAdapter, Ownable { + string public constant PROVIDER = "debridge"; + + IDeBridgeGate public immutable deBridgeGate; + + mapping(uint256 => address) public enabledReporters; + + error NotProxy(); + error UnauthorizedSender(address sender, uint256 chainId); + + event ReporterSet(uint256 indexed chainId, address indexed reporter); + + constructor(address _deBridgeGate) { + deBridgeGate = IDeBridgeGate(_deBridgeGate); + } + + function setReporterByChainId(uint256 chainId, address reporter) external onlyOwner { + enabledReporters[chainId] = reporter; + emit ReporterSet(chainId, reporter); + } + + function storeHashes(uint256[] calldata ids, bytes32[] calldata hashes) external { + ICallProxy callProxy = ICallProxy(deBridgeGate.callProxy()); + if (address(callProxy) != msg.sender) revert NotProxy(); + + address sender = address(uint160(bytes20(callProxy.submissionNativeSender()))); + uint256 chainId = callProxy.submissionChainIdFrom(); + if (enabledReporters[chainId] != sender) revert UnauthorizedSender(sender, chainId); + + _storeHashes(chainId, ids, hashes); + } +} diff --git a/packages/evm/contracts/adapters/DeBridge/DeBridgeReporter.sol b/packages/evm/contracts/adapters/DeBridge/DeBridgeReporter.sol new file mode 100644 index 00000000..fa25dca7 --- /dev/null +++ b/packages/evm/contracts/adapters/DeBridge/DeBridgeReporter.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.20; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { Reporter } from "../Reporter.sol"; +import { IDeBridgeGate } from "./interfaces/IDeBridgeGate.sol"; + +contract DeBridgeReporter is Reporter, Ownable { + string public constant PROVIDER = "debridge"; + + IDeBridgeGate public immutable deBridgeGate; + uint256 fee; + + event FeeSet(uint256 fee); + + constructor(address headerStorage, address yaho, address _deBridgeGate) Reporter(headerStorage, yaho) { + deBridgeGate = IDeBridgeGate(_deBridgeGate); + } + + function setFee(uint256 fee_) external onlyOwner { + fee = fee_; + emit FeeSet(fee); + } + + function _dispatch( + uint256 targetChainId, + address adapter, + uint256[] memory ids, + bytes32[] memory hashes + ) internal override returns (bytes32) { + bytes32 submissionId = deBridgeGate.sendMessage{ value: fee }( + targetChainId, + abi.encodePacked(adapter), + abi.encodeWithSignature("storeHashes(uint256[],bytes32[])", ids, hashes) + ); + return submissionId; + } +} diff --git a/packages/evm/contracts/adapters/DeBridge/interfaces/ICallProxy.sol b/packages/evm/contracts/adapters/DeBridge/interfaces/ICallProxy.sol new file mode 100644 index 00000000..5ada62d0 --- /dev/null +++ b/packages/evm/contracts/adapters/DeBridge/interfaces/ICallProxy.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.7; + +interface ICallProxy { + /// @dev Chain from which the current submission is received + function submissionChainIdFrom() external view returns (uint256); + + /// @dev Native sender of the current submission + function submissionNativeSender() external view returns (bytes memory); + + /// @dev Used for calls where native asset transfer is involved. + /// @param _reserveAddress Receiver of the tokens if the call to _receiver fails + /// @param _receiver Contract to be called + /// @param _data Call data + /// @param _flags Flags to change certain behavior of this function, see Flags library for more details + /// @param _nativeSender Native sender + /// @param _chainIdFrom Id of a chain that originated the request + function call( + address _reserveAddress, + address _receiver, + bytes memory _data, + uint256 _flags, + bytes memory _nativeSender, + uint256 _chainIdFrom + ) external payable returns (bool); + + /// @dev Used for calls where ERC20 transfer is involved. + /// @param _token Asset address + /// @param _reserveAddress Receiver of the tokens if the call to _receiver fails + /// @param _receiver Contract to be called + /// @param _data Call data + /// @param _flags Flags to change certain behavior of this function, see Flags library for more details + /// @param _nativeSender Native sender + /// @param _chainIdFrom Id of a chain that originated the request + function callERC20( + address _token, + address _reserveAddress, + address _receiver, + bytes memory _data, + uint256 _flags, + bytes memory _nativeSender, + uint256 _chainIdFrom + ) external returns (bool); +} diff --git a/packages/evm/contracts/adapters/DeBridge/interfaces/IDeBridgeGate.sol b/packages/evm/contracts/adapters/DeBridge/interfaces/IDeBridgeGate.sol new file mode 100644 index 00000000..8cc2883f --- /dev/null +++ b/packages/evm/contracts/adapters/DeBridge/interfaces/IDeBridgeGate.sol @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.7; + +interface IDeBridgeGate { + /* ========== STRUCTS ========== */ + + struct TokenInfo { + uint256 nativeChainId; + bytes nativeAddress; + } + + struct DebridgeInfo { + uint256 chainId; // native chain id + uint256 maxAmount; // maximum amount to transfer + uint256 balance; // total locked assets + uint256 lockedInStrategies; // total locked assets in strategy (AAVE, Compound, etc) + address tokenAddress; // asset address on the current chain + uint16 minReservesBps; // minimal hot reserves in basis points (1/10000) + bool exist; + } + + struct DebridgeFeeInfo { + uint256 collectedFees; // total collected fees + uint256 withdrawnFees; // fees that already withdrawn + mapping(uint256 => uint256) getChainFee; // whether the chain for the asset is supported + } + + struct ChainSupportInfo { + uint256 fixedNativeFee; // transfer fixed fee + bool isSupported; // whether the chain for the asset is supported + uint16 transferFeeBps; // transfer fee rate nominated in basis points (1/10000) of transferred amount + } + + struct DiscountInfo { + uint16 discountFixBps; // fix discount in BPS + uint16 discountTransferBps; // transfer % discount in BPS + } + + /// @param executionFee Fee paid to the transaction executor. + /// @param fallbackAddress Receiver of the tokens if the call fails. + struct SubmissionAutoParamsTo { + uint256 executionFee; + uint256 flags; + bytes fallbackAddress; + bytes data; + } + + /// @param executionFee Fee paid to the transaction executor. + /// @param fallbackAddress Receiver of the tokens if the call fails. + struct SubmissionAutoParamsFrom { + uint256 executionFee; + uint256 flags; + address fallbackAddress; + bytes data; + bytes nativeSender; + } + + struct FeeParams { + uint256 receivedAmount; + uint256 fixFee; + uint256 transferFee; + bool useAssetFee; + bool isNativeToken; + } + + /* ========== PUBLIC VARS GETTERS ========== */ + + /// @dev Returns whether the transfer with the submissionId was claimed. + /// submissionId is generated in getSubmissionIdFrom + function isSubmissionUsed(bytes32 submissionId) external view returns (bool); + + /// @dev Returns native token info by wrapped token address + function getNativeInfo(address token) external view returns (uint256 nativeChainId, bytes memory nativeAddress); + + /// @dev Returns address of the proxy to execute user's calls. + function callProxy() external view returns (address); + + /// @dev Fallback fixed fee in native asset, used if a chain fixed fee is set to 0 + function globalFixedNativeFee() external view returns (uint256); + + /// @dev Fallback transfer fee in BPS, used if a chain transfer fee is set to 0 + function globalTransferFeeBps() external view returns (uint16); + + /* ========== FUNCTIONS ========== */ + + /// @dev Submits the message to the deBridge infrastructure to be broadcasted to another supported blockchain (identified by _dstChainId) + /// with the instructions to call the _targetContractAddress contract using the given _targetContractCalldata + /// @notice NO ASSETS ARE BROADCASTED ALONG WITH THIS MESSAGE + /// @notice DeBridgeGate only accepts submissions with msg.value (native ether) covering a small protocol fee + /// (defined in the globalFixedNativeFee property). Any excess amount of ether passed to this function is + /// included in the message as the execution fee - the amount deBridgeGate would give as an incentive to + /// a third party in return for successful claim transaction execution on the destination chain. + /// @notice DeBridgeGate accepts a set of flags that control the behaviour of the execution. This simple method + /// sets the default set of flags: REVERT_IF_EXTERNAL_FAIL, PROXY_WITH_SENDER + /// @param _dstChainId ID of the destination chain. + /// @param _targetContractAddress A contract address to be called on the destination chain + /// @param _targetContractCalldata Calldata to execute against the target contract on the destination chain + function sendMessage( + uint256 _dstChainId, + bytes memory _targetContractAddress, + bytes memory _targetContractCalldata + ) external payable returns (bytes32 submissionId); + + /// @dev Submits the message to the deBridge infrastructure to be broadcasted to another supported blockchain (identified by _dstChainId) + /// with the instructions to call the _targetContractAddress contract using the given _targetContractCalldata + /// @notice NO ASSETS ARE BROADCASTED ALONG WITH THIS MESSAGE + /// @notice DeBridgeGate only accepts submissions with msg.value (native ether) covering a small protocol fee + /// (defined in the globalFixedNativeFee property). Any excess amount of ether passed to this function is + /// included in the message as the execution fee - the amount deBridgeGate would give as an incentive to + /// a third party in return for successful claim transaction execution on the destination chain. + /// @notice DeBridgeGate accepts a set of flags that control the behaviour of the execution. This simple method + /// sets the default set of flags: REVERT_IF_EXTERNAL_FAIL, PROXY_WITH_SENDER + /// @param _dstChainId ID of the destination chain. + /// @param _targetContractAddress A contract address to be called on the destination chain + /// @param _targetContractCalldata Calldata to execute against the target contract on the destination chain + /// @param _flags A bitmask of toggles listed in the Flags library + /// @param _referralCode Referral code to identify this submission + function sendMessage( + uint256 _dstChainId, + bytes memory _targetContractAddress, + bytes memory _targetContractCalldata, + uint256 _flags, + uint32 _referralCode + ) external payable returns (bytes32 submissionId); + + /// @dev This method is used for the transfer of assets [from the native chain](https://docs.debridge.finance/the-core-protocol/transfers#transfer-from-native-chain). + /// It locks an asset in the smart contract in the native chain and enables minting of deAsset on the secondary chain. + /// @param _tokenAddress Asset identifier. + /// @param _amount Amount to be transferred (note: the fee can be applied). + /// @param _chainIdTo Chain id of the target chain. + /// @param _receiver Receiver address. + /// @param _permitEnvelope Permit for approving the spender by signature. bytes (amount + deadline + signature) + /// @param _useAssetFee use assets fee for pay protocol fix (work only for specials token) + /// @param _referralCode Referral code + /// @param _autoParams Auto params for external call in target network + function send( + address _tokenAddress, + uint256 _amount, + uint256 _chainIdTo, + bytes memory _receiver, + bytes memory _permitEnvelope, + bool _useAssetFee, + uint32 _referralCode, + bytes calldata _autoParams + ) external payable returns (bytes32 submissionId); + + /// @dev Is used for transfers [into the native chain](https://docs.debridge.finance/the-core-protocol/transfers#transfer-from-secondary-chain-to-native-chain) + /// to unlock the designated amount of asset from collateral and transfer it to the receiver. + /// @param _debridgeId Asset identifier. + /// @param _amount Amount of the transferred asset (note: the fee can be applied). + /// @param _chainIdFrom Chain where submission was sent + /// @param _receiver Receiver address. + /// @param _nonce Submission id. + /// @param _signatures Validators signatures to confirm + /// @param _autoParams Auto params for external call + function claim( + bytes32 _debridgeId, + uint256 _amount, + uint256 _chainIdFrom, + address _receiver, + uint256 _nonce, + bytes calldata _signatures, + bytes calldata _autoParams + ) external; + + /// @dev Withdraw collected fees to feeProxy + /// @param _debridgeId Asset identifier. + function withdrawFee(bytes32 _debridgeId) external; + + /// @dev Returns asset fixed fee value for specified debridge and chainId. + /// @param _debridgeId Asset identifier. + /// @param _chainId Chain id. + function getDebridgeChainAssetFixedFee(bytes32 _debridgeId, uint256 _chainId) external view returns (uint256); + + /* ========== EVENTS ========== */ + + /// @dev Emitted once the tokens are sent from the original(native) chain to the other chain; the transfer tokens + /// are expected to be claimed by the users. + event Sent( + bytes32 submissionId, + bytes32 indexed debridgeId, + uint256 amount, + bytes receiver, + uint256 nonce, + uint256 indexed chainIdTo, + uint32 referralCode, + FeeParams feeParams, + bytes autoParams, + address nativeSender + // bool isNativeToken //added to feeParams + ); + + /// @dev Emitted once the tokens are transferred and withdrawn on a target chain + event Claimed( + bytes32 submissionId, + bytes32 indexed debridgeId, + uint256 amount, + address indexed receiver, + uint256 nonce, + uint256 indexed chainIdFrom, + bytes autoParams, + bool isNativeToken + ); + + /// @dev Emitted when new asset support is added. + event PairAdded( + bytes32 debridgeId, + address tokenAddress, + bytes nativeAddress, + uint256 indexed nativeChainId, + uint256 maxAmount, + uint16 minReservesBps + ); + + event MonitoringSendEvent(bytes32 submissionId, uint256 nonce, uint256 lockedOrMintedAmount, uint256 totalSupply); + + event MonitoringClaimEvent(bytes32 submissionId, uint256 lockedOrMintedAmount, uint256 totalSupply); + + /// @dev Emitted when the asset is allowed/disallowed to be transferred to the chain. + event ChainSupportUpdated(uint256 chainId, bool isSupported, bool isChainFrom); + /// @dev Emitted when the supported chains are updated. + event ChainsSupportUpdated(uint256 chainIds, ChainSupportInfo chainSupportInfo, bool isChainFrom); + + /// @dev Emitted when the new call proxy is set. + event CallProxyUpdated(address callProxy); + /// @dev Emitted when the transfer request is executed. + event AutoRequestExecuted(bytes32 submissionId, bool indexed success, address callProxy); + + /// @dev Emitted when a submission is blocked. + event Blocked(bytes32 submissionId); + /// @dev Emitted when a submission is unblocked. + event Unblocked(bytes32 submissionId); + + /// @dev Emitted when fee is withdrawn. + event WithdrawnFee(bytes32 debridgeId, uint256 fee); + + /// @dev Emitted when globalFixedNativeFee and globalTransferFeeBps are updated. + event FixedNativeFeeUpdated(uint256 globalFixedNativeFee, uint256 globalTransferFeeBps); + + /// @dev Emitted when globalFixedNativeFee is updated by feeContractUpdater + event FixedNativeFeeAutoUpdated(uint256 globalFixedNativeFee); +} From ccc83d1be1ae5250e0c1d2d269f235fcbca8a266 Mon Sep 17 00:00:00 2001 From: Alessandro Manfredi Date: Thu, 9 May 2024 14:46:20 +0200 Subject: [PATCH 2/3] refactor(evm): rn _deBridgeGate into deBridgeGate_ --- packages/evm/contracts/adapters/DeBridge/DeBridgeReporter.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/evm/contracts/adapters/DeBridge/DeBridgeReporter.sol b/packages/evm/contracts/adapters/DeBridge/DeBridgeReporter.sol index fa25dca7..42c383de 100644 --- a/packages/evm/contracts/adapters/DeBridge/DeBridgeReporter.sol +++ b/packages/evm/contracts/adapters/DeBridge/DeBridgeReporter.sol @@ -13,8 +13,8 @@ contract DeBridgeReporter is Reporter, Ownable { event FeeSet(uint256 fee); - constructor(address headerStorage, address yaho, address _deBridgeGate) Reporter(headerStorage, yaho) { - deBridgeGate = IDeBridgeGate(_deBridgeGate); + constructor(address headerStorage, address yaho, address deBridgeGate_) Reporter(headerStorage, yaho) { + deBridgeGate = IDeBridgeGate(deBridgeGate_); } function setFee(uint256 fee_) external onlyOwner { From 2380b6bc44bac7d245c22fc1663a4959a64b0b23 Mon Sep 17 00:00:00 2001 From: Alessandro Manfredi Date: Thu, 9 May 2024 14:47:10 +0200 Subject: [PATCH 3/3] feat(evm): adds debridge reporter & adapter deployment tasks --- .../evm/tasks/deploy/adapters/debridge.ts | 47 +++++++++++++++++++ packages/evm/tasks/deploy/adapters/index.ts | 1 + 2 files changed, 48 insertions(+) create mode 100644 packages/evm/tasks/deploy/adapters/debridge.ts diff --git a/packages/evm/tasks/deploy/adapters/debridge.ts b/packages/evm/tasks/deploy/adapters/debridge.ts new file mode 100644 index 00000000..20122a66 --- /dev/null +++ b/packages/evm/tasks/deploy/adapters/debridge.ts @@ -0,0 +1,47 @@ +import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers" +import { task } from "hardhat/config" +import type { TaskArguments } from "hardhat/types" + +import type { DeBridgeAdapter } from "../../../types/contracts/adapters/DeBridge/DeBridgeAdapter" +import type { DeBridgeReporter } from "../../../types/contracts/adapters/DeBridge/DeBridgeReporter" +import type { DeBridgeAdapter__factory } from "../../../types/factories/contracts/adapters/DeBridge/DeBridgeAdapter__factory" +import type { DeBridgeReporter__factory } from "../../../types/factories/contracts/adapters/DeBridge/DeBridgeReporter__factory" +import { verify } from "../index" + +task("deploy:DeBridgeAdapter") + .addParam("deBridgeGate", "address of the DeBridge gate contract") + .addFlag("verify", "whether to verify the contract on Etherscan") + .setAction(async function (taskArguments: TaskArguments, hre) { + console.log("Deploying DeBridgeAdapter...") + const signers: SignerWithAddress[] = await hre.ethers.getSigners() + const deBridgeAdapterFactory: DeBridgeAdapter__factory = ( + await hre.ethers.getContractFactory("DeBridgeAdapter") + ) + const constructorArguments = [taskArguments.deBridgeGate] as const + const deBridgeAdapter: DeBridgeAdapter = ( + await deBridgeAdapterFactory.connect(signers[0]).deploy(...constructorArguments) + ) + await deBridgeAdapter.deployed() + console.log("DeBridgeAdapter deployed to:", deBridgeAdapter.address) + if (taskArguments.verify) await verify(hre, deBridgeAdapter, constructorArguments) + }) + +task("deploy:DeBridgeReporter") + .addParam("headerStorage", "address of the header storage contract") + .addParam("yaho", "address of the Yaho contract") + .addParam("deBridgeGate", "address of the DeBridge gate contract") + .addFlag("verify", "whether to verify the contract on Etherscan") + .setAction(async function (taskArguments: TaskArguments, hre) { + console.log("Deploying DeBridgeReporter...") + const signers: SignerWithAddress[] = await hre.ethers.getSigners() + const deBridgeReporterFactory: DeBridgeReporter__factory = ( + await hre.ethers.getContractFactory("DeBridgeReporter") + ) + const constructorArguments = [taskArguments.headerStorage, taskArguments.yaho, taskArguments.deBridgeGate] as const + const deBridgeReporter: DeBridgeReporter = ( + await deBridgeReporterFactory.connect(signers[0]).deploy(...constructorArguments) + ) + await deBridgeReporter.deployed() + console.log("DeBridgeReporter deployed to:", deBridgeReporter.address) + if (taskArguments.verify) await verify(hre, deBridgeReporter, constructorArguments) + }) diff --git a/packages/evm/tasks/deploy/adapters/index.ts b/packages/evm/tasks/deploy/adapters/index.ts index c6130790..fd14dcb2 100644 --- a/packages/evm/tasks/deploy/adapters/index.ts +++ b/packages/evm/tasks/deploy/adapters/index.ts @@ -4,6 +4,7 @@ import "./axiom" import "./ccip" import "./celer" import "./connext" +import "./debridge" import "./dendreth" import "./electron" import "./hyperlane"