From ec49eee31038964b556d8764b9507348399433bf Mon Sep 17 00:00:00 2001 From: JayJay1024 Date: Thu, 1 Feb 2024 12:44:23 +0800 Subject: [PATCH] Improve code content and ux --- .../DesktopBuildingWithMsgport.tsx | 30 +- .../MobileBuildingWithMsgport.tsx | 1 + .../BuildingWithMsgport/MobileMenuItem.tsx | 6 +- src/components/BuildingWithMsgport/data.ts | 164 +++++++- src/components/UseCase/Item.tsx | 7 +- src/components/UseCase/data.ts | 243 +++++++++++ src/components/UseCase/index.tsx | 25 +- src/data/code/test.ts | 380 ------------------ 8 files changed, 426 insertions(+), 430 deletions(-) create mode 100644 src/components/UseCase/data.ts delete mode 100644 src/data/code/test.ts diff --git a/src/components/BuildingWithMsgport/DesktopBuildingWithMsgport.tsx b/src/components/BuildingWithMsgport/DesktopBuildingWithMsgport.tsx index 113e080b..5f00c21f 100644 --- a/src/components/BuildingWithMsgport/DesktopBuildingWithMsgport.tsx +++ b/src/components/BuildingWithMsgport/DesktopBuildingWithMsgport.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import PrettyCode from "../PrettyCode"; import RightArrowRound from "../RightArrowRound"; import { link, menu, title } from "./data"; @@ -8,6 +8,14 @@ export default function DesktopBuildingWithMsgport() { const { isDesktopHeight } = useApp(); const [activeTitle, setActiveTitle] = useState(menu[0].title); const activeMenu = menu.find(({ title }) => title === activeTitle) || menu[0]; + const ref = useRef(null); + const [refHeight, setRefHeight] = useState(0); + + useEffect(() => { + setTimeout(() => { + setRefHeight(ref.current?.clientHeight ?? 0); + }, 0); + }, [activeTitle]); return (
@@ -46,15 +54,25 @@ export default function DesktopBuildingWithMsgport() {
-
-
+
+

{activeMenu.title}

- {activeMenu.description} + {activeMenu.description}
diff --git a/src/components/BuildingWithMsgport/MobileBuildingWithMsgport.tsx b/src/components/BuildingWithMsgport/MobileBuildingWithMsgport.tsx index c81d9e62..09ad74ca 100644 --- a/src/components/BuildingWithMsgport/MobileBuildingWithMsgport.tsx +++ b/src/components/BuildingWithMsgport/MobileBuildingWithMsgport.tsx @@ -19,6 +19,7 @@ export default function MobileBuildingWithMsgport() { title={m.title} description={m.description} code={m.code} + language={m.language} onChange={setActiveActive} /> ))} diff --git a/src/components/BuildingWithMsgport/MobileMenuItem.tsx b/src/components/BuildingWithMsgport/MobileMenuItem.tsx index 602cf3a1..6a332f33 100644 --- a/src/components/BuildingWithMsgport/MobileMenuItem.tsx +++ b/src/components/BuildingWithMsgport/MobileMenuItem.tsx @@ -6,6 +6,7 @@ interface Props { activeTitle: string; description: string; code: string; + language: "solidity" | "javascript"; onChange?: (title: string) => void; } @@ -14,6 +15,7 @@ export default function MobileMenuItem({ activeTitle, description, code, + language, onChange = () => undefined, }: Props) { const [btnHeight, setBtnHeight] = useState(); @@ -40,8 +42,8 @@ export default function MobileMenuItem({ style={{ height: isActive ? ref.current?.clientHeight : 0 }} >
- {description} - + {description ? {description} : null} +
diff --git a/src/components/BuildingWithMsgport/data.ts b/src/components/BuildingWithMsgport/data.ts index 71c5d685..d358607f 100644 --- a/src/components/BuildingWithMsgport/data.ts +++ b/src/components/BuildingWithMsgport/data.ts @@ -1,33 +1,161 @@ -import { testCode } from "../../data/code/test"; - export const title: string[] = ["Start building with", "Darwinia Msgport"]; export const link: string = "https://docs.darwinia.network/simplest-messaging-demo-045164a7d4d7413ba4a5dd65214e59e6"; // External link -export const menu: { title: string; description: string; code: string }[] = [ +export const menu: { title: string; description: string; code: string; language: "solidity" | "javascript" }[] = [ { - title: "Prepare to Receive Messages", - description: "Create a workspace for the contract that needs to be deployed on the target chain.", - code: testCode, + title: "Msgport Interface", + description: + "This interface provides developers with a generic message passing interface to send arbitrary data between contracts on different blockchain networks.", + code: `// This file is part of Darwinia. +// Copyright (C) 2018-2023 Darwinia Network +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.0; + +interface IMessagePort { + error MessageFailure(bytes errorData); + + /// @dev Send a cross-chain message over the MessagePort. + /// @notice Send a cross-chain message over the MessagePort. + /// @param toChainId The message destination chain id. + /// @param toDapp The user application contract address which receive the message. + /// @param message The calldata which encoded by ABI Encoding. + /// @param params Extend parameters to adapt to different message protocols. + function send(uint256 toChainId, address toDapp, bytes calldata message, bytes calldata params) external payable; + + /// @notice Get a quote in source native gas, for the amount that send() requires to pay for message delivery. + /// It should be noted that not all ports will implement this interface. + /// @dev If the messaging protocol does not support on-chain fetch fee, then revert with "Unimplemented!". + /// @param toChainId The message destination chain id. + /// @param toDapp The user application contract address which receive the message. + /// @param message The calldata which encoded by ABI Encoding. + /// @param params Extend parameters to adapt to different message protocols. + function fee(uint256 toChainId, address toDapp, bytes calldata message, bytes calldata params) + external + view + returns (uint256); +}`, + language: "solidity", }, { - title: "Deploy MyReceiverDapp", - description: "Deploy the contract on the target chain.", - code: testCode, + title: "Deploy ExampleReceiverDapp", + description: "Deploy a receiver contract on the target chain to receive messages. (for example purposes only)", + code: `// This file is part of Darwinia. +// Copyright (C) 2018-2023 Darwinia Network +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.17; + +import "https://github.com/darwinia-network/darwinia-msgport/blob/main/src/user/Application.sol"; + +contract ExampleReceiverDapp is Application { + event DappMessageRecv(uint256 fromChainId, address fromDapp, address localPort, bytes message); + + // local port address + address public immutable PORT; + // remote dapp address + address public immutable DAPP; + + constructor(address port, address dapp) { + PORT = port; + DAPP = dapp; + } + + /// @notice You could check the fromDapp address or messagePort address. + function testReceive(bytes calldata message) external { + uint256 fromChainId = _fromChainId(); + address fromDapp = _xmsgSender(); + address localPort = _msgPort(); + require(localPort == PORT); + require(fromDapp == DAPP); + emit DappMessageRecv(fromChainId, fromDapp, localPort, message); + } +}`, + language: "solidity", }, { - title: "Get Calldata", - description: "Input the message and retrieve the calldata.", - code: testCode, + title: "Encode calldata", + description: "Build the remote call data as the message payload.", + code: `import { ethers } from 'ethers'; + +const privateKey = process.env.PRIVATE_KEY; +const providerUrl = ; +const receiverDappAddr = ; + +function encodeReceiveCall() { + const receiverABI = [{ + "inputs": [ + { + "internalType": "bytes", + "name": "message", + "type": "bytes" + } + ], + "name": "testReceive", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }]; + const testMessage = "0x1234"; + const provider = new ethers.JsonRpcProvider(providerUrl); + const signer = new ethers.Wallet(privateKey, provider); + const receiverDapp = new ethers.Contract(receiverDappAddr, receiverABI, signer); + const callData = receiverDapp.interface.encodeFunctionData('testReceive', [testMessage]); + console.log(callData); +} + +encodeReceiveCall();`, + language: "javascript", }, { - title: "Get Fee and Messaging Params", - description: "Use the MsgPort API to obtain the cross-chain message fee and messaging service parameters.", - code: testCode, + title: "Get fee and params from Msgport API", + description: "Estimate fee and get adaptation params from Msgport API.", + code: `import axios from 'axios'; + +async function getFeeParams() { + const requestBody = { + 'from_chain_id': , + 'to_chain_id': , + 'payload': , + 'from_address': , + 'to_address': , + 'refund_address': , + }; + const result = await axios.get("https://msgport-api.darwinia.network/ormp/fee", { params: requestBody }); + const { fee, params } = result.data.data; + console.log(fee, params); +} + +await getFeeParams();`, + language: "javascript", }, { - title: "Sending Message", - description: "Use the contract to send the message.", - code: testCode, + title: "Sending message", + description: "", + code: `// This file is part of Darwinia. +// Copyright (C) 2018-2023 Darwinia Network +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.17; + +import "https://github.com/darwinia-network/darwinia-msgport/blob/main/src/interfaces/IMessagePort.sol"; + +contract ExampleSenderDapp { + event DappMessageSent(address localPort, bytes message); + + // local port address + address public immutable PORT; + + constructor(address port) { + PORT = port; + } + + function testSend(uint256 toChainId, address toDapp, bytes calldata message, bytes calldata params) external payable{ + IMessagePort(PORT).send{value: msg.value}(toChainId, toDapp, message, params); + emit DappMessageSent(PORT, message); + } +}`, + language: "solidity", }, ]; diff --git a/src/components/UseCase/Item.tsx b/src/components/UseCase/Item.tsx index 07b8ea03..9e0a2214 100644 --- a/src/components/UseCase/Item.tsx +++ b/src/components/UseCase/Item.tsx @@ -8,9 +8,10 @@ interface Props { code: string; link: string; // External link description: string; + language: "solidity" | "javascript"; } -export default function Item({ title, code, link, description }: Props) { +export default function Item({ title, code, link, description, language }: Props) { const [isHovering, setIsHovering] = useState(false); const { isDesktopWidth, isDesktopHeight } = useApp(); @@ -36,10 +37,10 @@ export default function Item({ title, code, link, description }: Props) { className={`lg:w-[48.75rem] ${ isDesktopWidth ? (isDesktopHeight ? "h-[26.5rem]" : "h-[19.25rem]") : "h-[26.5rem]" }`} - language="solidity" + language={language} code={code} /> {description} ); -} \ No newline at end of file +} diff --git a/src/components/UseCase/data.ts b/src/components/UseCase/data.ts new file mode 100644 index 00000000..6c2b8ba1 --- /dev/null +++ b/src/components/UseCase/data.ts @@ -0,0 +1,243 @@ +export const cases: { + title: string; + code: string; + link: string; + description: string; + language: "solidity" | "javascript"; +}[] = [ + { + title: "xAccount", + code: `// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @dev ISafeXAccountFactory represents a factory contract responsible for the creation of SafeXAccounts. +interface ISafeXAccountFactory { + /// @dev Cross chian function for create SafeXAccount on target chain. + /// @notice If recovery address is \`address(0)\`, do not enabale recovery module. + /// @param code Port code that used for create SafeXAccount. + /// @param toChainId Target chain id. + /// @param params Port params correspond with the port. + /// @param recovery The default safe recovery module address on target chain for SafeXAccount. + function xCreate(bytes4 code, uint256 toChainId, bytes calldata params, address recovery) external payable; + /// @dev Calculate SafeXAccount address on target chain. + /// @notice The module address is only effective during its creation and may be replaced by the SafeXAccount in the future. + /// @param fromChainId Chain id that SafeXAccount belongs in. + /// @param toChainId Chain id that SafeXAccount lives in. + /// @param deployer Owner that SafeXAccount belongs to. + /// @return (SafeXAccount address, module address). + function safeXAccountOf(uint256 fromChainId, uint256 toChainId, address deployer) + external + view + returns (address, address); +} + +/// @dev ISafeMsgportModule serves as a module integrated within the Safe system, specifically devised to enable remote administration and control of the safeXAccount. +interface ISafeMsgportModule { + /// @dev Receive xCall from root chain xOwner. + /// @param target Target of the transaction that should be executed + /// @param value Wei value of the transaction that should be executed + /// @param data Data of the transaction that should be executed + /// @param operation Operation (Call or Delegatecall) of the transaction that should be executed + /// @return xExecute return data Return data after xCall. + function xExecute(address target, uint256 value, bytes calldata data, uint8 operation) + external + payable + returns (bytes memory); +} + +/// @dev IPortRegistry functions as a comprehensive registry for all chain message ports. +interface IPortRegistry { + /// @dev Fetch port address by chainId and port code. + function get(uint256 chainId, bytes4 code) external view returns (address); +} + +/// @dev IMessagePort serves as a universal interface facilitating the transmission of cross-chain messages across all msgport channels. +interface IMessagePort { + /// @dev Send a cross-chain message over the MessagePort. + /// @notice Send a cross-chain message over the MessagePort. + /// @param toChainId The message destination chain id. + /// @param toDapp The user application contract address which receive the message. + /// @param message The calldata which encoded by ABI Encoding. + /// @param params Extend parameters to adapt to different message protocols. + function send(uint256 toChainId, address toDapp, bytes calldata message, bytes calldata params) external payable; +} + +/// @dev SafeXAccountDemo is a demonstration showcasing the utilization of SafeXAccount to execute an xCall. +contract ExampleSafeXAccount { + // SafeXAccountFactory address + address public factory; + // PortRegistry address + address public registry; + + constructor(address factory_, address registry_) { + factory = factory_; + registry = registry_; + } + + /// @dev The function is utilized to create a SafeXAccount on the target chain. + function createXAccountOnTargetChain(bytes4 code, uint256 toChainId, bytes calldata params, address recovery) + public + payable + { + ISafeXAccountFactory(factory).xCreate{value: msg.value}(code, toChainId, params, recovery); + } + + /// @dev The function facilitates the execution of an xCall across a SafeXAccount. + function crossChainCall( + bytes4 code, + uint256 toChainId, + bytes calldata params, + address target, + uint256 value, + bytes calldata data, + uint8 operation + ) public payable { + bytes memory message = + abi.encodeWithSelector(ISafeMsgportModule.xExecute.selector, target, value, data, operation); + address port = IPortRegistry(registry).get(toChainId, code); + (, address module) = ISafeXAccountFactory(factory).safeXAccountOf(block.chainid, toChainId, address(this)); + IMessagePort(port).send{value: msg.value}(toChainId, module, message, params); + } +}`, + link: "https://docs.darwinia.network/cross-chain-abstract-account-a0aae327c3d54555818fdab1149d0608", + description: + "xAccount is a component within Darwinia Msgport, xAccount simplifies the user experience in executing cross-chain operations.", + language: "solidity", + }, + { + title: "Asset Bridge", + code: `// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; +interface ILnDefaultBridgeSource { + function transferAndLockMargin( + Snapshot calldata _snapshot, + uint112 _amount, + address _receiver + ) external; + function requestSlashAndRemoteRelease( + LnBridgeHelper.TransferParameter calldata _params, + uint256 _remoteChainId, + bytes32 _expectedTransferId, + bytes memory _extParams + ) external; + function requestWithdrawMargin( + uint256 _remoteChainId, + address _sourceToken, + address _targetToken, + uint112 _amount, + bytes memory _extParams + ) external; +} +interface ILnDefaultBridgeTarget { + function transferAndReleaseMargin( + LnBridgeHelper.TransferParameter calldata _params, + uint256 _remoteChainId, + bytes32 _expectedTransferId + ) external; + function withdraw( + uint256 _remoteChainId, + bytes32 _lastTransferId, + uint64 _withdrawNonce, + address _provider, + address _sourceToken, + address _targetToken, + uint112 _amount + ) external; + function slash( + LnBridgeHelper.TransferParameter memory _params, + uint256 _remoteChainId, + address _slasher, + uint112 _fee, + uint112 _penalty + ) external; +} +contract MsglineMessager is Application, AccessController { + IMessageLine public immutable msgline; + struct RemoteMessager { + uint256 msglineRemoteChainId; + address messager; + } + mapping(address=>bool) public whiteList; + // app remoteChainId => msgline remote messager + mapping(uint256=>RemoteMessager) public remoteMessagers; + // token bridge pair + // hash(msglineRemoteChainId, localAppAddress) => remoteAppAddress + mapping(bytes32=>address) public remoteAppReceivers; + mapping(bytes32=>address) public remoteAppSenders; + event CallerUnMatched(uint256 srcAppChainId, address srcAppAddress); + event CallResult(uint256 srcAppChainId, bool result); + modifier onlyWhiteList() { + require(whiteList[msg.sender], "msg.sender not in whitelist"); + _; + } + modifier onlyMsgline() { + require(msg.sender == address(msgline), "invalid caller"); + _; + } + constructor(address _dao, address _msgline) { + _initialize(_dao); + msgline = IMessageLine(_msgline); + } + function setRemoteMessager(uint256 _appRemoteChainId, uint256 _msglineRemoteChainId, address _remoteMessager) onlyDao external { + remoteMessagers[_appRemoteChainId] = RemoteMessager(_msglineRemoteChainId, _remoteMessager); + } + function setWhiteList(address _caller, bool _enable) external onlyDao { + whiteList[_caller] = _enable; + } + function registerRemoteReceiver(uint256 _remoteChainId, address _remoteBridge) onlyWhiteList external { + RemoteMessager memory remoteMessager = remoteMessagers[_remoteChainId]; + require(remoteMessager.messager != address(0), "remote not configured"); + bytes32 key = keccak256(abi.encodePacked(remoteMessager.msglineRemoteChainId, msg.sender)); + remoteAppReceivers[key] = _remoteBridge; + } + function registerRemoteSender(uint256 _remoteChainId, address _remoteBridge) onlyWhiteList external { + RemoteMessager memory remoteMessager = remoteMessagers[_remoteChainId]; + require(remoteMessager.messager != address(0), "remote not configured"); + bytes32 key = keccak256(abi.encodePacked(remoteMessager.msglineRemoteChainId, msg.sender)); + remoteAppSenders[key] = _remoteBridge; + } + function sendMessage(uint256 _remoteChainId, bytes memory _message, bytes memory _params) onlyWhiteList external payable { + RemoteMessager memory remoteMessager = remoteMessagers[_remoteChainId]; + require(remoteMessager.messager != address(0), "remote not configured"); + bytes32 key = keccak256(abi.encodePacked(remoteMessager.msglineRemoteChainId, msg.sender)); + address remoteAppAddress = remoteAppReceivers[key]; + require(remoteAppAddress != address(0), "app pair not registered"); + bytes memory msglinePayload = messagePayload(msg.sender, remoteAppAddress, _message); + msgline.send{ value: msg.value }( + remoteMessager.msglineRemoteChainId, + remoteMessager.messager, + msglinePayload, + _params + ); + } + function receiveMessage(uint256 _srcAppChainId, address _remoteAppAddress, address _localAppAddress, bytes memory _message) onlyMsgline external { + uint256 srcChainId = _fromChainId(); + RemoteMessager memory remoteMessager = remoteMessagers[_srcAppChainId]; + require(srcChainId == remoteMessager.msglineRemoteChainId, "invalid remote chainid"); + require(remoteMessager.messager == _xmsgSender(), "invalid remote messager"); + bytes32 key = keccak256(abi.encodePacked(srcChainId, _localAppAddress)); + // check remote appSender + if (_remoteAppAddress != remoteAppSenders[key]) { + emit CallerUnMatched(_srcAppChainId, _remoteAppAddress); + return; + } + (bool success,) = _localAppAddress.call(_message); + // don't revert to prevent message block + emit CallResult(_srcAppChainId, success); + } + function messagePayload(address _from, address _to, bytes memory _message) public view returns(bytes memory) { + return abi.encodeWithSelector( + MsglineMessager.receiveMessage.selector, + block.chainid, + _from, + _to, + _message + ); + } +}`, + link: "https://docs.darwinia.network/tokenasset-bridge-c2b42b6e0c3348ddb91333a4f24ac4d9", + description: + "By utilizing Darwinia Msgport, DApp developers can facilitate efficient cross-chain transfer and management of assets, streamlining the process of moving assets between different blockchain networks.", + language: "solidity", + }, +]; diff --git a/src/components/UseCase/index.tsx b/src/components/UseCase/index.tsx index 2c537fff..d5f0e197 100644 --- a/src/components/UseCase/index.tsx +++ b/src/components/UseCase/index.tsx @@ -1,29 +1,12 @@ -import { testCode } from "../../data/code/test"; import Item from "./Item"; - -const cases: { title: string; code: string; link: string; description: string }[] = [ - { - title: "Governance", - code: testCode, - link: "https://docs.darwinia.network/multi-chain-dao-governance-d3b2e194828d4af8bad8e3fa28219fc3", - description: - "DApp developers can utilize Darwinia Msgport xAccount feature for cross-chain execution of governance decisions.", - }, - { - title: "Asset Bridge", - code: testCode, - link: "https://docs.darwinia.network/tokenasset-bridge-c2b42b6e0c3348ddb91333a4f24ac4d9", - description: - "By utilizing Darwinia Msgport, DApp developers can facilitate efficient cross-chain transfer and management of assets, streamlining the process of moving assets between different blockchain networks.", - }, -]; +import { cases } from "./data"; export default function UseCase() { return (
- {cases.map(({ title, code, link, description }) => ( - + {cases.map(({ title, code, link, description, language }) => ( + ))}
); -} \ No newline at end of file +} diff --git a/src/data/code/test.ts b/src/data/code/test.ts deleted file mode 100644 index 9abcbd2c..00000000 --- a/src/data/code/test.ts +++ /dev/null @@ -1,380 +0,0 @@ -export const testCode = `// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import "@zeppelin-solidity/contracts/security/Pausable.sol"; -import "./LnBridgeHelper.sol"; -import "../interface/ILnDefaultBridgeTarget.sol"; - -/// @title LnDefaultBridgeSource -/// @notice LnDefaultBridgeSource is a contract to help user transfer token to liquidity node and generate proof, -/// then the liquidity node must transfer the same amount of the token to the user on target chain. -/// Otherwise if timeout the slasher can send a slash request message to target chain, then force transfer from lnProvider's margin to the user. -/// @dev See https://github.com/helix-bridge/contracts/tree/master/helix-contract -contract LnDefaultBridgeSource is Pausable { - // provider fee is paid to liquidity node's account - // the fee is charged by the same token that user transfered - // providerFee = baseFee + liquidityFeeRate/LIQUIDITY_FEE_RATE_BASE * sendAmount - struct SourceProviderConfigure { - uint112 baseFee; - uint16 liquidityFeeRate; - uint64 withdrawNonce; - bool pause; - } - - struct SourceProviderInfo { - SourceProviderConfigure config; - // we use this nonce to generate the unique withdraw id - bytes32 lastTransferId; - } - // the Snapshot is the state of the token bridge when user prepare to transfer across chains. - // If the snapshot updated when the across chain transfer confirmed, it will - // 1. if lastTransferId or withdrawNonce updated, revert - // 2. if totalFee increase, revert - // 3. if totalFee decrease, success - struct Snapshot { - uint256 remoteChainId; - address provider; - address sourceToken; - address targetToken; - bytes32 transferId; - uint112 totalFee; - uint64 withdrawNonce; - } - - // lock info - // the fee and penalty is the state of the transfer confirmed - struct LockInfo { - uint112 fee; - uint112 penalty; - // the timestamp when token locked, if zero, the lockinfo not exist - uint32 timestamp; - } - - // hash(remoteChainId, sourceToken, targetToken) => token info - mapping(bytes32=>LnBridgeHelper.TokenInfo) public tokenInfos; - // hash(remoteChainId, provider, sourceToken, targetToken) => provider info - mapping(bytes32=>SourceProviderInfo) public srcProviders; - // transferId => lock info - mapping(bytes32=>LockInfo) public lockInfos; - - address public protocolFeeReceiver; - - event TokenLocked( - uint256 remoteChainId, - bytes32 transferId, - address provider, - address sourceToken, - address targetToken, - uint112 amount, - uint112 fee, - uint32 timestamp, - address receiver); - event LnProviderUpdated(uint256 remoteChainId, address provider, address sourceToken, address targetToken, uint112 baseFee, uint8 liquidityfeeRate); - - event WithdrawMarginRequest(uint256 remoteChainId, address sourceToken, address targetToken, uint112 amount); - event SlashRequest(uint256 remoteChainId, address sourceToken, address targetToken, bytes32 expectedTransferId); - - // protocolFeeReceiver is the protocol fee reciever, we don't use the contract itself as the receiver - /// @notice should be called by special privileges - function _updateFeeReceiver(address _feeReceiver) internal { - require(_feeReceiver != address(this), "invalid system fee receiver"); - protocolFeeReceiver = _feeReceiver; - } - - // register or update token info, it can be only called by contract owner - // source token can only map a unique target token on target chain - /// @notice should be called by special privileges - function _setTokenInfo( - uint256 _remoteChainId, - address _sourceToken, - address _targetToken, - uint112 _protocolFee, - uint112 _penaltyLnCollateral, - uint8 _sourceDecimals, - uint8 _targetDecimals - ) internal { - bytes32 key = keccak256(abi.encodePacked(_remoteChainId, _sourceToken, _targetToken)); - tokenInfos[key] = LnBridgeHelper.TokenInfo( - _protocolFee, - _penaltyLnCollateral, - _sourceDecimals, - _targetDecimals, - true - ); - } - - function providerPause(uint256 _remoteChainId, address _sourceToken, address _targetToken) external { - bytes32 providerKey = LnBridgeHelper.getProviderKey(_remoteChainId, msg.sender, _sourceToken, _targetToken); - srcProviders[providerKey].config.pause = true; - } - - function providerUnpause(uint256 _remoteChainId, address _sourceToken, address _targetToken) external { - bytes32 providerKey = LnBridgeHelper.getProviderKey(_remoteChainId, msg.sender, _sourceToken, _targetToken); - srcProviders[providerKey].config.pause = false; - } - - // lnProvider register - // 1. set fee on source chain - // 2. deposit margin on target chain - function setProviderFee( - uint256 _remoteChainId, - address _sourceToken, - address _targetToken, - uint112 _baseFee, - uint8 _liquidityFeeRate - ) external { - bytes32 key = keccak256(abi.encodePacked(_remoteChainId, _sourceToken, _targetToken)); - LnBridgeHelper.TokenInfo memory tokenInfo = tokenInfos[key]; - require(tokenInfo.isRegistered, "token not registered"); - bytes32 providerKey = LnBridgeHelper.getProviderKey(_remoteChainId, msg.sender, _sourceToken, _targetToken); - - require(_liquidityFeeRate < LnBridgeHelper.LIQUIDITY_FEE_RATE_BASE, "liquidity fee too large"); - SourceProviderConfigure memory providerConfigure = srcProviders[providerKey].config; - providerConfigure.baseFee = _baseFee; - providerConfigure.liquidityFeeRate = _liquidityFeeRate; - - // we only update the field fee of the provider info - // if the provider has not been registered, then this line will register, otherwise update fee - srcProviders[providerKey].config = providerConfigure; - - emit LnProviderUpdated(_remoteChainId, msg.sender, _sourceToken, _targetToken, _baseFee, _liquidityFeeRate); - } - - // the fee user should paid when transfer. - // totalFee = providerFee + protocolFee - function totalFee( - uint256 _remoteChainId, - address _provider, - address _sourceToken, - address _targetToken, - uint112 _amount - ) external view returns(uint256) { - bytes32 key = keccak256(abi.encodePacked(_remoteChainId, _sourceToken, _targetToken)); - LnBridgeHelper.TokenInfo memory tokenInfo = tokenInfos[key]; - bytes32 providerKey = LnBridgeHelper.getProviderKey(_remoteChainId, _provider, _sourceToken, _targetToken); - SourceProviderInfo memory providerInfo = srcProviders[providerKey]; - uint112 providerFee = LnBridgeHelper.calculateProviderFee(providerInfo.config.baseFee, providerInfo.config.liquidityFeeRate, _amount); - return providerFee + tokenInfo.protocolFee; - } - - // This function transfers tokens from the user to LnProvider and generates a proof on the source chain. - // The snapshot represents the state of the LN bridge for this LnProvider, obtained by the off-chain indexer. - // If the chain state is updated and does not match the snapshot state, the transaction will be reverted. - // 1. the state(lastTransferId, fee, withdrawNonce) must match snapshot - // 2. transferId not exist - function transferAndLockMargin( - Snapshot calldata _snapshot, - uint112 _amount, - address _receiver - ) whenNotPaused external payable { - require(_amount > 0, "invalid amount"); - LnBridgeHelper.TokenInfo memory tokenInfo = tokenInfos[ - LnBridgeHelper.getTokenKey(_snapshot.remoteChainId, _snapshot.sourceToken, _snapshot.targetToken) - ]; - require(tokenInfo.isRegistered, "token not registered"); - - bytes32 providerKey = LnBridgeHelper.getProviderKey(_snapshot.remoteChainId, _snapshot.provider, _snapshot.sourceToken, _snapshot.targetToken); - - SourceProviderInfo memory providerInfo = srcProviders[providerKey]; - require(!providerInfo.config.pause, "provider paused"); - uint112 providerFee = LnBridgeHelper.calculateProviderFee(providerInfo.config.baseFee, providerInfo.config.liquidityFeeRate, _amount); - - // the chain state not match snapshot - require(providerInfo.lastTransferId == _snapshot.transferId, "snapshot expired:transfer"); - require(_snapshot.withdrawNonce == providerInfo.config.withdrawNonce, "snapshot expired:withdraw"); - require(_snapshot.totalFee >= providerFee + tokenInfo.protocolFee && providerFee > 0, "fee is invalid"); - - uint112 targetAmount = LnBridgeHelper.sourceAmountToTargetAmount(tokenInfo, _amount); - require(targetAmount > 0, "invalid target amount"); - require(block.timestamp < type(uint32).max, "timestamp overflow"); - bytes32 transferId = keccak256(abi.encodePacked( - block.chainid, - _snapshot.remoteChainId, - _snapshot.transferId, - _snapshot.provider, - _snapshot.sourceToken, - _snapshot.targetToken, - _receiver, - targetAmount - )); - require(lockInfos[transferId].timestamp == 0, "transferId exist"); - // if the transfer refund, then the fee and penalty should be given to slasher, but the protocol fee is ignored - // and we use the penalty value configure at the moment transfer confirmed - lockInfos[transferId] = LockInfo(providerFee, tokenInfo.penaltyLnCollateral, uint32(block.timestamp)); - - // update the state to prevent other transfers using the same snapshot - srcProviders[providerKey].lastTransferId = transferId; - - if (_snapshot.sourceToken == address(0)) { - require(_amount + _snapshot.totalFee == msg.value, "amount unmatched"); - LnBridgeHelper.safeTransferNative(_snapshot.provider, _amount + providerFee); - if (tokenInfo.protocolFee > 0) { - LnBridgeHelper.safeTransferNative(protocolFeeReceiver, tokenInfo.protocolFee); - } - uint256 refund = _snapshot.totalFee - tokenInfo.protocolFee - providerFee; - if ( refund > 0 ) { - LnBridgeHelper.safeTransferNative(msg.sender, refund); - } - } else { - LnBridgeHelper.safeTransferFrom( - _snapshot.sourceToken, - msg.sender, - _snapshot.provider, - _amount + providerFee - ); - if (tokenInfo.protocolFee > 0) { - LnBridgeHelper.safeTransferFrom( - _snapshot.sourceToken, - msg.sender, - protocolFeeReceiver, - tokenInfo.protocolFee - ); - } - } - emit TokenLocked( - _snapshot.remoteChainId, - transferId, - _snapshot.provider, - _snapshot.sourceToken, - _snapshot.targetToken, - targetAmount, - providerFee, - uint32(block.timestamp), - _receiver); - } - - function _slashAndRemoteReleaseCall( - LnBridgeHelper.TransferParameter memory _params, - uint256 _remoteChainId, - bytes32 _expectedTransferId - ) internal view returns(bytes memory message) { - bytes32 key = keccak256(abi.encodePacked(_remoteChainId, _params.sourceToken, _params.targetToken)); - LnBridgeHelper.TokenInfo memory tokenInfo = tokenInfos[key]; - require(tokenInfo.isRegistered, "token not registered"); - uint112 targetAmount = LnBridgeHelper.sourceAmountToTargetAmount(tokenInfo, _params.amount); - - bytes32 transferId = keccak256(abi.encodePacked( - block.chainid, - _remoteChainId, - _params.previousTransferId, - _params.provider, - _params.sourceToken, - _params.targetToken, - _params.receiver, - targetAmount - )); - require(_expectedTransferId == transferId, "expected transfer id not match"); - LockInfo memory lockInfo = lockInfos[transferId]; - require(lockInfo.timestamp == _params.timestamp, "invalid timestamp"); - require(block.timestamp > lockInfo.timestamp + LnBridgeHelper.SLASH_EXPIRE_TIME, "invalid timestamp"); - uint112 targetFee = LnBridgeHelper.sourceAmountToTargetAmount(tokenInfo, lockInfo.fee); - uint112 targetPenalty = LnBridgeHelper.sourceAmountToTargetAmount(tokenInfo, lockInfo.penalty); - - message = abi.encodeWithSelector( - ILnDefaultBridgeTarget.slash.selector, - _params, - block.chainid, - msg.sender, // slasher - targetFee, - targetPenalty - ); - } - - function _withdrawMarginCall( - uint256 _remoteChainId, - address _sourceToken, - address _targetToken, - uint112 _amount - ) internal returns(bytes memory message) { - bytes32 key = keccak256(abi.encodePacked(_remoteChainId, _sourceToken, _targetToken)); - LnBridgeHelper.TokenInfo memory tokenInfo = tokenInfos[key]; - require(tokenInfo.isRegistered, "token not registered"); - - bytes32 providerKey = LnBridgeHelper.getProviderKey(_remoteChainId, msg.sender, _sourceToken, _targetToken); - SourceProviderInfo memory providerInfo = srcProviders[providerKey]; - srcProviders[providerKey].config.withdrawNonce = providerInfo.config.withdrawNonce + 1; - uint112 targetAmount = LnBridgeHelper.sourceAmountToTargetAmount(tokenInfo, _amount); - message = abi.encodeWithSelector( - ILnDefaultBridgeTarget.withdraw.selector, - block.chainid, - providerInfo.lastTransferId, - providerInfo.config.withdrawNonce + 1, - msg.sender, //provider, - _sourceToken, - _targetToken, - targetAmount - ); - } - - function encodeSlashCall( - LnBridgeHelper.TransferParameter memory _params, - uint256 _localChainId, - address _slasher, - uint112 _fee, - uint112 _penalty - ) public pure returns(bytes memory message) { - return abi.encodeWithSelector( - ILnDefaultBridgeTarget.slash.selector, - _params, - _localChainId, - _slasher, - _fee, - _penalty - ); - } - - function encodeWithdrawCall( - bytes32 _lastTransferId, - uint256 _localChainId, - uint64 _withdrawNonce, - address _provider, - address _sourceToken, - address _targetToken, - uint112 _amount - ) public pure returns(bytes memory message) { - return abi.encodeWithSelector( - ILnDefaultBridgeTarget.withdraw.selector, - _localChainId, - _lastTransferId, - _withdrawNonce, - _provider, - _sourceToken, - _targetToken, - _amount - ); - } - - function _sendMessageToTarget(uint256 _remoteChainId, bytes memory _payload, bytes memory _extParams) internal virtual {} - - function requestSlashAndRemoteRelease( - LnBridgeHelper.TransferParameter calldata _params, - uint256 _remoteChainId, - bytes32 _expectedTransferId, - bytes memory _extParams - ) payable external { - bytes memory slashCallMessage = _slashAndRemoteReleaseCall( - _params, - _remoteChainId, - _expectedTransferId - ); - _sendMessageToTarget(_remoteChainId, slashCallMessage, _extParams); - emit SlashRequest(_remoteChainId, _params.sourceToken, _params.targetToken, _expectedTransferId); - } - - function requestWithdrawMargin( - uint256 _remoteChainId, - address _sourceToken, - address _targetToken, - uint112 _amount, - bytes memory _extParams - ) payable external { - bytes memory withdrawCallMessage = _withdrawMarginCall( - _remoteChainId, - _sourceToken, - _targetToken, - _amount - ); - _sendMessageToTarget(_remoteChainId, withdrawCallMessage, _extParams); - emit WithdrawMarginRequest(_remoteChainId, _sourceToken, _targetToken, _amount); - } -}`;