diff --git a/docs/.gitbook/assets/DlnExternalCallAdapter.abi.json b/docs/.gitbook/assets/DlnExternalCallAdapter.abi.json new file mode 100644 index 00000000..4b19a346 --- /dev/null +++ b/docs/.gitbook/assets/DlnExternalCallAdapter.abi.json @@ -0,0 +1,771 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AdminBadRole", + "type": "error" + }, + { + "inputs": [], + "name": "BadRole", + "type": "error" + }, + { + "inputs": [], + "name": "DisabledDelayedExecution", + "type": "error" + }, + { + "inputs": [], + "name": "DlnBadRole", + "type": "error" + }, + { + "inputs": [], + "name": "EthTransferFailed", + "type": "error" + }, + { + "inputs": [], + "name": "FailedExecuteExternalCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "executionFee", + "type": "uint256" + } + ], + "name": "IncorrectExecutionFee", + "type": "error" + }, + { + "inputs": [], + "name": "InvalideAmount", + "type": "error" + }, + { + "inputs": [], + "name": "InvalideState", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "UnknownEnvelopeVersion", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldExecutor", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newExecutor", + "type": "address" + } + ], + "name": "ExecutorUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "callId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "orderId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "cancelBeneficiary", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ExternalCallCancelled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "orderId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bool", + "name": "callSucceeded", + "type": "bool" + } + ], + "name": "ExternalCallExecuted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "orderId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "callResult", + "type": "bytes" + } + ], + "name": "ExternalCallFailed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "callId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "orderId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "callAuthority", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "externalCall", + "type": "bytes" + } + ], + "name": "ExternallCallRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_orderId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_callAuthority", + "type": "address" + }, + { + "internalType": "address", + "name": "_tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_tokenAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "_externalCallHash", + "type": "bytes32" + } + ], + "name": "cancelCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "defaultExecutor", + "outputs": [ + { + "internalType": "contract IExternalCallExecutor", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "dlnDestination", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_orderId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_callAuthority", + "type": "address" + }, + { + "internalType": "address", + "name": "_tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_tokenAmount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_externalCall", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_rewardBeneficiary", + "type": "address" + } + ], + "name": "executeCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "externalCallStatus", + "outputs": [ + { + "internalType": "enum DlnExternalCallAdapter.CallStatus", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_orderId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_callAuthority", + "type": "address" + }, + { + "internalType": "address", + "name": "_tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_transferredAmount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_externalCallHash", + "type": "bytes32" + } + ], + "name": "getCallId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_orderId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_callAuthority", + "type": "address" + }, + { + "internalType": "address", + "name": "_tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_transferredAmount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_externalCall", + "type": "bytes" + } + ], + "name": "getCallId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_dlnDestination", + "type": "address" + }, + { + "internalType": "address", + "name": "_executor", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_orderId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_callAuthority", + "type": "address" + }, + { + "internalType": "address", + "name": "_tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_transferredAmount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_externalCall", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_externalCallRewardBeneficiary", + "type": "address" + } + ], + "name": "receiveCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "tokenBalanceHistory", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newExecutor", + "type": "address" + } + ], + "name": "updateExecutor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] \ No newline at end of file diff --git a/docs/.gitbook/assets/DlnExternalCallLib.sol b/docs/.gitbook/assets/DlnExternalCallLib.sol new file mode 100644 index 00000000..f13014fe --- /dev/null +++ b/docs/.gitbook/assets/DlnExternalCallLib.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.7; + +library DlnExternalCallLib { + struct ExternalCallEnvelopV1 { + // Address that will receive takeToken if ext call failed + address fallbackAddress; + // *optional. Smart contract that will execute ext call. + address executorAddress; + // fee that will pay for executor who will execute ext call + uint160 executionFee; + // If false, the taker must execute an external call with fulfill in a single transaction. + bool allowDelayedExecution; + // if true transaction that will execute ext call will fail if ext call is not success + bool requireSuccessfullExecution; + bytes payload; + } + + struct ExternalCallPayload { + // the address of the contract to call + address to; + // *optional. Tx gas for execute ext call + uint32 txGas; + bytes callData; + } +} diff --git a/docs/.gitbook/assets/ExternalCallExecutor.abi.json b/docs/.gitbook/assets/ExternalCallExecutor.abi.json new file mode 100644 index 00000000..bffc928d --- /dev/null +++ b/docs/.gitbook/assets/ExternalCallExecutor.abi.json @@ -0,0 +1,369 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AdapterBadRole", + "type": "error" + }, + { + "inputs": [], + "name": "AdminBadRole", + "type": "error" + }, + { + "inputs": [], + "name": "CallFailed", + "type": "error" + }, + { + "inputs": [], + "name": "EthTransferFailed", + "type": "error" + }, + { + "inputs": [], + "name": "NotEnoughTxGas", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "inputs": [], + "name": "ADAPTER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "isProhibitedSelector", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_transferredAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_fallbackAddress", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_payload", + "type": "bytes" + } + ], + "name": "onERC20Received", + "outputs": [ + { + "internalType": "bool", + "name": "callSucceeded", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "callResult", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_fallbackAddress", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_payload", + "type": "bytes" + } + ], + "name": "onEtherReceived", + "outputs": [ + { + "internalType": "bool", + "name": "callSucceeded", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "callResult", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "rescueFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } + ] \ No newline at end of file diff --git a/docs/.gitbook/assets/IExternalCallExecutor.sol b/docs/.gitbook/assets/IExternalCallExecutor.sol new file mode 100644 index 00000000..63d52607 --- /dev/null +++ b/docs/.gitbook/assets/IExternalCallExecutor.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IExternalCallExecutor { + /** + * @notice Handles the receipt of Ether to the contract, then validates and executes a function call. + * @dev Only callable by the adapter. This function decodes the payload to extract execution data. + * If the function specified in the callData is prohibited, or the recipient contract is zero, + * all Ether is transferred to the fallback address. + * Otherwise, it attempts to execute the function call. Any remaining Ether is then transferred to the fallback address. + * @param _orderId The ID of the order that triggered this function. + * @param _fallbackAddress The address to receive any unspent Ether. + * @param _payload The encoded data containing the execution data. + * @return callSucceeded A boolean indicating whether the call was successful. + * @return callResult The data returned from the call. + */ + function onEtherReceived( + bytes32 _orderId, + address _fallbackAddress, + bytes memory _payload + ) external payable returns (bool callSucceeded, bytes memory callResult); + + /** + * @notice Handles the receipt of ERC20 tokens, validates and executes a function call. + * @dev Only callable by the adapter. This function decodes the payload to extract execution data. + * If the function specified in the callData is prohibited, or the recipient contract is zero, + * all received tokens are transferred to the fallback address. + * Otherwise, it attempts to execute the function call. Any remaining tokens are then transferred to the fallback address. + * @param _orderId The ID of the order that triggered this function. + * @param _token The address of the ERC20 token that was transferred. + * @param _transferredAmount The amount of tokens transferred. + * @param _fallbackAddress The address to receive any unspent tokens. + * @param _payload The encoded data containing the execution data. + * @return callSucceeded A boolean indicating whether the call was successful. + * @return callResult The data returned from the call. + */ + function onERC20Received( + bytes32 _orderId, + address _token, + uint256 _transferredAmount, + address _fallbackAddress, + bytes memory _payload + ) external returns (bool callSucceeded, bytes memory callResult); +} diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 90e76827..556a8c52 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -44,6 +44,7 @@ * [Introduction](dln-the-debridge-liquidity-network-protocol/introduction.md) * [Protocol Overview](dln-the-debridge-liquidity-network-protocol/protocol-overview.md) +* [DLN Hooks](dln-the-debridge-liquidity-network-protocol/dln-hooks.md) * [Fees and Supported Chains](dln-the-debridge-liquidity-network-protocol/fees-and-supported-chains.md) * [🟢 Deployed Contracts](dln-the-debridge-liquidity-network-protocol/deployed-contracts.md) * [Market and Limit Orders](dln-the-debridge-liquidity-network-protocol/market-and-limit-orders.md) @@ -52,10 +53,11 @@ * [Placing orders](dln-the-debridge-liquidity-network-protocol/interacting-with-smart-contracts/placing-orders.md) * [Filling orders](dln-the-debridge-liquidity-network-protocol/interacting-with-smart-contracts/filling-orders.md) * [Withdrawing Affiliate Fees](dln-the-debridge-liquidity-network-protocol/interacting-with-smart-contracts/withdrawing-affiliate-fees.md) - * [Creating Calldata for Solana](dln-the-debridge-liquidity-network-protocol/interacting-with-smart-contracts/creating-calldata-for-solana.md) * [Interacting with the API](dln-the-debridge-liquidity-network-protocol/interacting-with-the-api/README.md) * [Authentication](dln-the-debridge-liquidity-network-protocol/interacting-with-the-api/authentication.md) * [Requesting Order Creation Transaction](dln-the-debridge-liquidity-network-protocol/interacting-with-the-api/requesting-order-creation-transaction.md) + * [Integrating DLN hooks](dln-the-debridge-liquidity-network-protocol/interacting-with-the-api/integrating-dln-hooks/README.md) + * [Creating Hook data for Solana](dln-the-debridge-liquidity-network-protocol/interacting-with-the-api/integrating-dln-hooks/creating-hook-data-for-solana.md) * [Submitting an Order Creation Transaction](dln-the-debridge-liquidity-network-protocol/interacting-with-the-api/submitting-an-order-creation-transaction.md) * [Tracking a Status of the Order](dln-the-debridge-liquidity-network-protocol/interacting-with-the-api/tracking-a-status-of-the-order.md) * [Cancelling the Order](dln-the-debridge-liquidity-network-protocol/interacting-with-the-api/cancelling-the-order.md) @@ -63,6 +65,11 @@ * [Interacting with the deBridge App](dln-the-debridge-liquidity-network-protocol/interacting-with-the-debridge-app/README.md) * [Custom Linking](dln-the-debridge-liquidity-network-protocol/interacting-with-the-debridge-app/custom-linking.md) * [deBridge Widget](dln-the-debridge-liquidity-network-protocol/debridge-widget.md) +* [Protocol specs](dln-the-debridge-liquidity-network-protocol/protocol-specs/README.md) + * [Deterministic order ID](dln-the-debridge-liquidity-network-protocol/protocol-specs/deterministic-order-id.md) + * [Hook data](dln-the-debridge-liquidity-network-protocol/protocol-specs/hook-data/README.md) + * [Anatomy of a Hook for the EVM-based chains](dln-the-debridge-liquidity-network-protocol/protocol-specs/hook-data/anatomy-of-a-hook-for-the-evm-based-chains.md) + * [Anatomy of a Hook for Solana](dln-the-debridge-liquidity-network-protocol/protocol-specs/hook-data/anatomy-of-a-hook-for-solana.md) ## 💸 dePort diff --git a/docs/dln-the-debridge-liquidity-network-protocol/deployed-contracts.md b/docs/dln-the-debridge-liquidity-network-protocol/deployed-contracts.md index 6ee1fd2c..279d23a1 100644 --- a/docs/dln-the-debridge-liquidity-network-protocol/deployed-contracts.md +++ b/docs/dln-the-debridge-liquidity-network-protocol/deployed-contracts.md @@ -9,7 +9,7 @@ The following smart contracts have been deployed across supported chains to powe | `DlnSource` | [src5qyZHqTqecJV4aY6Cb6zDZLMDzrDKKezs22MPHr4](https://solscan.io/account/src5qyZHqTqecJV4aY6Cb6zDZLMDzrDKKezs22MPHr4) | Used to place orders on DLN | | `DlnDestination` | [dst5MGcFPoBeREFAA5E3tU5ij8m5uVYwkzkSAbsLbNo](https://solscan.io/account/dst5MGcFPoBeREFAA5E3tU5ij8m5uVYwkzkSAbsLbNo) | Used to fulfill and cancel DLN orders | -### Solana Contracts IDLs +#### IDLs {% file src="../.gitbook/assets/dst.ts" %} DLN Destination @@ -19,18 +19,26 @@ DLN Destination DLN Source {% endfile %} -### EVM Chains +### EVM-based chains -| Smart contract | Address | Description | -| ---------------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -| `DlnSource` | `0xeF4fB24aD0916217251F553c0596F8Edc630EB66` | Used to place orders on DLN | -| `DlnDestination` | `0xe7351fd770a37282b91d153ee690b63579d6dd7f` | Used to fulfill and cancel orders placed on DLN | -| `CrosschainForwarder` | `0x663dc15d3c1ac63ff12e45ab68fea3f0a883c251` | Intermediary smart contract used exclusively by the DLN API to sell input assets for trusted liquid assets prior order created, if necessary. | -| `ExternalCallExecutor` | `0xFC2CA4022d26AD4dCb3866ae30669669F6A28f19` | Used to accepting tokens and data to execute dln external calls | +| Smart contract | Address | Description | +| ------------------------ | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `DlnSource` | `0xeF4fB24aD0916217251F553c0596F8Edc630EB66` | Used to place orders on DLN | +| `DlnDestination` | `0xe7351fd770a37282b91d153ee690b63579d6dd7f` | Used to fulfill and cancel orders placed on DLN | +| `DlnExternalCallAdapter` | `0x61eF2e01E603aEB5Cd96F9eC9AE76cc6A68f6cF9` | Intermediary store and the engine for [DLN Hooks](dln-hooks.md) that implement `IExternalCallExecutor` (see below) | +| `ExternalCallExecutor` | `0xFC2CA4022d26AD4dCb3866ae30669669F6A28f19` | [Universal DLN Hook](protocol-specs/hook-data/anatomy-of-a-hook-for-the-evm-based-chains.md) (implementing `IExternalCallExecutor`) that executes arbitrary transaction calls bypassed via a payload | +| `CrosschainForwarder` | `0x663dc15d3c1ac63ff12e45ab68fea3f0a883c251` | Intermediary smart contract used exclusively by the DLN API to sell input assets for trusted liquid assets prior order created, if necessary. | -### **EVM Contracts ABIs** +#### **ABIs and interfaces** -[DlnSource.abi.json](https://3251284410-files.gitbook.io/\~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F8SH9Mg0oTRdKFNHxWvTP%2Fuploads%2F2Cg4HetCApwZBZxkThpT%2FDlnSource.abi.json?alt=media\&token=0bf21e5f-7511-4cc0-86a2-841a1bd70f3a) +{% file src="../.gitbook/assets/DlnSource.abi.json" %} -[DlnDestination.abi.json](https://3251284410-files.gitbook.io/\~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F8SH9Mg0oTRdKFNHxWvTP%2Fuploads%2FyhXoBlUK16nqXPM3vSgM%2FDlnDestination.abi.json?alt=media\&token=397802d5-e1bf-4743-95fc-8d248210ed38) +{% file src="../.gitbook/assets/DlnDestination.abi.json" %} +{% file src="../.gitbook/assets/ExternalCallExecutor.abi.json" %} + +{% file src="../.gitbook/assets/DlnExternalCallAdapter.abi.json" %} + +{% file src="../.gitbook/assets/IExternalCallExecutor.sol" %} + +{% file src="../.gitbook/assets/DlnExternalCallLib.sol" %} diff --git a/docs/dln-the-debridge-liquidity-network-protocol/dln-hooks.md b/docs/dln-the-debridge-liquidity-network-protocol/dln-hooks.md new file mode 100644 index 00000000..1433fe4d --- /dev/null +++ b/docs/dln-the-debridge-liquidity-network-protocol/dln-hooks.md @@ -0,0 +1,86 @@ +--- +description: >- + DLN Hooks is a core feature of the DLN protocol that allows users, protocols + and market makers to attach arbitrary on-chain actions to the orders that + would get executed upon their fulfillment. +--- + +# DLN Hooks + +The DLN protocol allows users to place cross-chain orders with an arbitrary on-chain action attached as an inseparable part of it, enabling cryptographically signed operations to be performed on the destination chain upon order fulfillment. + +An action itself — called _a hook_ — is a raw destination chain-specific data, that represents either an instruction (or a set of instructions) to be executed on Solana, or a hook enriched with a custom payload, or even a transaction call to be executed on a EVM-based chain. A hook can perform actions of any complexity, including operations on the outcome of the order. This effectively enriches the interactions between users and protocols, enabling cross-chain communications that were not possible before. A few possible use cases: + +* **asset distribution**: one can place a cross-chain order that buys an asset and immediately distributes it across a set of addresses; +* **blockchain abstraction**: a dApp can place a cross-chain order that buys an asset and deposits it onto the staking protocol on behalf of a signing user; +* **user onboarding**: a service can place a cross-chain order (from a user's familiar blockchain to a blockchain a user has not tried before) that buys a stable coin and tops up user's wallet with a small amount of native blockchain currency to enable the user to start submitting transactions right away; +* **action triggers**: a cross-chain order could trigger conditional actions that emit events, change state, even to prevent an order from being filled; +* and anything else you might thought about! + +Additionally, a hook is bundled with a bunch of properties (a hook metadata) that define hook behaviour. + +*** + +### Hooks are a part of an order + +Orders are identified by [deterministic order ID](protocol-specs/deterministic-order-id.md), and the hash of the hook's raw data is essentially a part of this ID (see the last two fields of the [deterministic order ID reference](protocol-specs/deterministic-order-id.md)), so once a user signs-off an order with a hook, he eventually makes a cryptographic confirmation that he is willing to sell input asset for output asset AND execute a specific action along with the output asset on the destination chain. + +The proper execution of the hook during order fulfillment is guaranteed by [`DlnDestination`](deployed-contracts.md) — the DLN smart contract responsible for filling orders, which ensures that the given hook along with other properties of the order actually match the given order ID. This means that trying to spoof a hook would lead to a different ID of a non-existent order, so the solver is compelled to pass a specific hook data. + +### Hooks are trustless + +DLN is an open market, so anyone can place arbitrary orders (even non-profitable, or having fake hooks), and anyone with available liquidity can be a solver and fill any open order. The [`DlnDestination`](deployed-contracts.md) smart contract simply ensures that requested amount is provided in full and further forwarded either to the recipient or to a hook's target (via the [`DlnExternalCallAdapter`](deployed-contracts.md) smart contract). This means that a hook's target should be designed with an assumption that anyone can place and fill arbitrary orders with arbitrary hooks, and thus never expose permissions only on the fact that the caller is a trusted DLN contract, because the DLN contract here is only an intermediary, not a guard. + +### Hooks atomicity + +Even though hooks are an inseparable part of DLN orders, their execution scenario is a matter of hook configuration. + +Hooks can be **success-required** and **optional-success**: + +* **success-required hooks** ARE REQUIRED to finish successfully. If the hook gets reverted, entire transaction gets reverted as well, which is guaranteed by the DLN smart contract. +* **optional-success hooks** ARE ALLOWED to fail. In the event of failure, the DLN smart contract would send the order's outcome further to the **fallback address** specified as a part of a hook envelope. + +Hooks can be atomic and non-atomic: + +* **atomic hooks** ARE REQUIRED be executed atomically along with the order fulfillment. In other words, the order with such hook is either filled and the hook is executed, or not at all. Mind that if the hook is an **success-required hook** and it fails, the entire order would not get filled, and the order's authority would need to cancel the order. If the hook an **optional-success hook** and it fails, then the order would get filled, and the order's outcome would get sent further to the **fallback address** specified as a part of a hook envelope. +* **non-atomic hooks** ARE ALLOWED (but not required) to be executed later, which is up to the solver who fills the order. If the solver fills the order but does not execute a hook, the order is marked is filled, but its outcome is stored securely in the DLN intermediary contract along with the hook, waiting until anyone (a trustless third party, like a solver) initiates a transaction to execute the hook, OR until the trusted authority of an order on the destination chain cancels the hook to receive the order's outcome in full. + +Smart contracts on the EVM-based chains support all options above. Smart contracts on Solana only support **non-atomic** **success-required hooks** due to limitations of this platform. + +### Hook cancellation policy + +Even though hooks are indivisible part of orders placed onto DLN, their execution and cancellation flow may vary. + +Atomic hooks are executed along with an order fulfillment, so they either succeed, or silently fail (if they are **optional-success hooks**), or revert and prevent the order from getting filled (if they are **success-required hooks**). In the worst case scenario, when they get reverted, the trusted authority of an order in the destination chain may only cancel entire order. + +Non-atomic hooks are allowed to be executed later in a separate transaction after an order gets filled. In this case, the hook (along with the outcome of an order) remains pending execution in the intermediary smart contract, and the trusted authority of an order on the destination chain may cancel the hook and receive the order's outcome in full. + +### Who pays for hook execution? + +Submitting a transaction to execute a hook implies paying a transaction fee. The DLN Hooks engine provides two distinct ways to incentivize solvers and other trustless third parties to submit transactions to execute hooks. + +The most straightforward way to cover hook execution is to lay the cost in the spread of an order: say, there is an order to sell 105 USDC on Solana and buy 100 USDC on Ethereum with a hook that deposits the bought amount to the LP: in this case, the difference between sell amount and buy amount (5 USDC) must cover all the fees and costs, including the cost of this hook execution. This is the preferred approach for **atomic hooks** that target **EVM-based chains**, because in this case the hook is the part of the execution flow of a transaction that fills the order. + +Additionally, a hook metadata may explicitly define a reward that the DLN Hooks engine contract should cut off an order outcome (before the outcome is transferred to a hook) in favor of a solver who pays for a transaction: for example, there could be an order to sell 106 USDC on Ethereum, buy 101 USDC on Solana with a hook that deposits exactly 100 USDC to the LP and leaves 1 USDC as a reward. This approach works for non-atomic hooks, and the smart contract guarantees that a solver would get exactly the specified amount of the outcome. + +The DLN API simplifies a hook's cost estimation by automatically simulating transaction upon order creation. + +### Common pitfalls + +A common source of frustration is a blockchain where a hook is expected to run: hooks are built for destination chains. For example, an order that sells SOL on Solana and buys ETH on Ethereum would get placed on Solana with the hook data encoded specifically for EVM, and vice versa. + +Atomic **success-required hooks** that get reverted would prevent their orders from getting fulfilled, causing users' funds to stuck, which would require users to initiate [a cancellation procedure](interacting-with-the-api/cancelling-the-order.md). This increases friction and worsens overall user experience, so it is advised to carefully test hooks and estimate potential fulfillments prior placing orders with such hooks. The API [takes the burden](interacting-with-the-api/integrating-dln-hooks/) of proper hook data validation, encoding, and hook simulation, ensuring that an order could get filled on the destination chain. + +### Examples + +* [Order from Ethereum to Solana](https://app.debridge.finance/order?orderId=0xd78af2a21f4c7dc1fb11e85fff608739d8af167b4ff91b03bc3a097822fcc966) with a non-atomic hook +* [Order from Ethereum to Polygon](https://app.debridge.finance/order?orderId=0x401c8eb93a1358bbe2924e98446ed35fbb73dabd638aa183de3aca0af2582a40) with an atomic success-required hook + +### Availability + +DLN Hooks are available on all supported blockchains. Hooks can be encoded programmatically while interacting directly with smart contracts, or passed to the DLN API via a simple high level interface. + +Further reading: + +* Easy usage with the DLN API: [integrating-dln-hooks](interacting-with-the-api/integrating-dln-hooks/ "mention") +* Technical specification: [hook-data](protocol-specs/hook-data/ "mention") diff --git a/docs/dln-the-debridge-liquidity-network-protocol/interacting-with-the-api/integrating-dln-hooks/README.md b/docs/dln-the-debridge-liquidity-network-protocol/interacting-with-the-api/integrating-dln-hooks/README.md new file mode 100644 index 00000000..cbff0432 --- /dev/null +++ b/docs/dln-the-debridge-liquidity-network-protocol/interacting-with-the-api/integrating-dln-hooks/README.md @@ -0,0 +1,177 @@ +# Integrating DLN hooks + +The DLN API provides a convenient high-level interface to attach hooks to orders upon [requesting an order creation transaction](../requesting-order-creation-transaction.md). The API takes the burden of proper hook data validation, encoding, cost estimation, and simulation, ensuring that an order would get filled on the destination chain and there is no technical inconsistencies that may prevent it. This is especially important for **atomic** **success-required hooks**, as an error during such hook execution would prevent an order from getting filled, and an order's authority would need to initiate [a cancellation procedure](../cancelling-the-order.md) from the destination chain, which increases friction and worsens UX. + +To specify the hook, use the `dlnHook` parameter of the [`create-tx`](https://dln.debridge.finance/v1.0#/DLN/DlnOrderControllerV10\_createOrder) endpoint. The value for this parameter must be a JSON in a specific format that describes the hook for the given destination chain. Depending on the destination chain, different templates are available. + +### Serialized instructions hook for Solana + +To set the hook to be executed upon filling order on Solana (`dstChainId=7565164`), the following template should be used: + +```javascript +{ type: "solana_serialized_instructions"; data: "0x..." } +``` + +where data is represented as a versioned transaction with one or more instructions. Thus, only **non-atomic** **success-required hooks** are supported. + +To craft a proper versioned transaction, use the guide: [creating-hook-data-for-solana.md](creating-hook-data-for-solana.md "mention") + +
+ +Example + +Example of encoding `dlnHook` parameter: + +```javascript +dlnHook: JSON.stringify({ + type: "solana_serialized_instructions"; + data: "0x000000000000000001000000000000000000000000010000000000000000010000000000000000135d3e6be2a2ae5274cfe4007df2f62ed31c618f048337fd1435ca2dee2a0d5d12000000000000001968562fef0aab1b1d8f99d44306595cd4ba41d7cc899c007a774d23ad702ff60101fd97b70d573d364ef44769540777b1ecdc21b88ff7def38c45d020e271c589dc00010000000000000000000000000000000000000000000000000000000000000000000062584959deb8a728a91cebdc187b545d920479265052145f31fb80c73fac5aea00009845350d27001686985bbc5c4a3646c928d7de27ddab09f2de56d21537201c4300019845350d27001686985bbc5c4a3646c928d7de27ddab09f2de56d21537201c4300010c1e5ba8fe0ec5c5e44d12c2b6317a8781ca19ddb0e1532b136f418e1d588f9500010c1e5ba8fe0ec5c5e44d12c2b6317a8781ca19ddb0e1532b136f418e1d588f95000106ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a900000000000000000000000000000000000000000000000000000000000000000000000006a7d517187bd16635dad40455fdc2c0c124c68f215675a5dbbacb5f0800000000008c97258f4e2489f1bb3d1029148e0d830b5a1399daff1084048e7bd8dbe9f8590000ef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000974d0b69f43d964da48af118a28037ab921a95eae1e637497b22867ce416255a00016be7362bf52e4ea29063d29ab43a832253ed7c68b62cf68e8d706a2f9531c1eb0001c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d610000040000000000000000736f6c" +}) +``` + +
+ +### Transaction call hook for EVM + +To easily attach an atomic success-required hook that executes an arbitrary transaction call via the default [Universal hook](../../protocol-specs/hook-data/anatomy-of-a-hook-for-the-evm-based-chains.md#universal-hook), the DLN API provides a simple shortcut for that: + +```javascript +{ + type: "evm_transaction_call"; + data: { + "to": "0x...", + "calldata": "0x...", + "gas": 0 + } +} +``` + +The `data.to` and `data.calldata` properties represent the transaction call that should be made, as explained in the [Universal hook](../../protocol-specs/hook-data/anatomy-of-a-hook-for-the-evm-based-chains.md#universal-hook) section. The `gas` property **must** be specified if: + +* the underlying call handles errors gracefully, which leads to underestimation of gas (see [our investigation](https://twitter.com/AlexSmirnov\_\_/status/1538903343455772673) on this) +* the transaction call can't be estimated currently, which leads to inability of the DLN API to properly estimate transaction costs. + +
+ +Example of a hook that deposits 0.1 USDC to AAVE + +The following snippet produces a `dlnHook` parameter that results a hook to deposit 0.1 USDC to AAVE on behalf of `0xc31dcE63f4B284Cf3bFb93A278F970204409747f`: + +```javascript +const query = new URLSearchParams({ + // other parameters were omitted for clarity + dstChainId: 137, + dstChainTokenOut: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359', + dstChainTokenOutAmount: '100000', + dlnHook: JSON.stringify({ + type: "evm_transaction_call"; + data: { + "to": "0x794a61358D6845594F94dc1DB02A252b5b4814aD", + "calldata": "0x617ba0370000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c335900000000000000000000000000000000000000000000000000000000000186a0000000000000000000000000c31dce63f4b284cf3bfb93a278f970204409747f0000000000000000000000000000000000000000000000000000000000000000", + "gas": 0 + } + }) +}) +``` + +
+ +This simple shortcut would be transparently converted by the DLN API to a hook with the following properties: + +```javascript +{ + fallbackAddress: dstChainTokenOutRecipient, + target: '0x0000000000000000000000000000000000000000', + reward: 0, + isNonAtomic: false, + isSuccessRequired: true, + targetPayload: { + to: dlnHook.data.to, + callData: dlnHook.calldata.data, + gas: dlnHook.data.gas + } +} +``` + +### Arbitrary hook for EVM + +To provide a complete customization of a hook, the DLN API offers a template that fully replicates the [HookDataV1 struct](../../protocol-specs/hook-data/anatomy-of-a-hook-for-the-evm-based-chains.md#hook-data-layout): + +```json +{ + "type": "evm_hook_data_v1", + "data": { + "fallbackAddress": "0x...", + "target": "0x...", + "reward": "0", + "isNonAtomic": boolean, + "isSuccessRequired": boolean, + "targetPayload": "0x" + } +} +``` + +The DLN API would encode it and inject into an order. + +
+ +Example of a hook that deposits 0.1 USDC to AAVE + +The following snippet produces a `dlnHook` parameter that results a hook to deposit 0.1 USDC to AAVE on behalf of `0xc31dcE63f4B284Cf3bFb93A278F970204409747f` via the default Univarsal hook: + +```javascript +const query = new URLSearchParams({ + // other parameters were omitted for clarity + dstChainId: 137, + dstChainTokenOut: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359', + dstChainTokenOutAmount: '100000', + dlnHook: JSON.stringify({ + type: "evm_hook_data_v1"; + data: { + "fallbackAddress": "0xc31dcE63f4B284Cf3bFb93A278F970204409747f", + "target": "0x0000000000000000000000000000000000000000", + "reward": "0", + "isNonAtomic": false, + "isSuccessRequired": true, + "targetPayload": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000794a61358d6845594f94dc1db02a252b5b4814ad000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000084617ba0370000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c335900000000000000000000000000000000000000000000000000000000000186a0000000000000000000000000c31dce63f4b284cf3bfb93a278f970204409747f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + }) +}) +``` + +
+ +### Hook validity considerations + +{% hint style="warning" %} +To ensure best and frictionless user experience, the DLN API would refuse to return a transaction to create an order if it is impossible to fulfill an order with the given hook. +{% endhint %} + +The hook is attached to the order during order's placement on the source chain, however actual fulfillment occurs on the destination chain. If the attached hook is success-required and exits unsuccessfully upon fulfillment, it prevents the entire order from getting filled. This would necessitate an order's authority to initiate [a cancellation procedure](../cancelling-the-order.md) from the destination chain, which increases friction and worsens UX. + +To prevent this, the DLN API constructs a potential transaction to fulfill the order to be created, and simulates this transaction internally to ensure that the order to be created could actually be filled. If such simulation causes an error, that points to the problem within the hook, the API would refuse to return a transaction, but would return an error with details instead, so you can debug the potential fulfillment transaction to find a pitfalls in the hook: + +```typescript +{ + "errorId": "HOOK_FAILED", + "errorPayload": { + "potentialFulfillOrderTxSimulation": { + "simulationInput": { + "chainId": number, + "blockNumber": number, + "tx": { + "from": string, + "to": string, + "data": string, + "value"?: string + }, + }, + + "simulationError": { + "errorName": string, + "data": string, + } + } + } +} +``` diff --git a/docs/dln-the-debridge-liquidity-network-protocol/interacting-with-smart-contracts/creating-calldata-for-solana.md b/docs/dln-the-debridge-liquidity-network-protocol/interacting-with-the-api/integrating-dln-hooks/creating-hook-data-for-solana.md similarity index 100% rename from docs/dln-the-debridge-liquidity-network-protocol/interacting-with-smart-contracts/creating-calldata-for-solana.md rename to docs/dln-the-debridge-liquidity-network-protocol/interacting-with-the-api/integrating-dln-hooks/creating-hook-data-for-solana.md diff --git a/docs/dln-the-debridge-liquidity-network-protocol/protocol-specs/README.md b/docs/dln-the-debridge-liquidity-network-protocol/protocol-specs/README.md new file mode 100644 index 00000000..4adbc1eb --- /dev/null +++ b/docs/dln-the-debridge-liquidity-network-protocol/protocol-specs/README.md @@ -0,0 +1,2 @@ +# Protocol specs + diff --git a/docs/dln-the-debridge-liquidity-network-protocol/protocol-specs/deterministic-order-id.md b/docs/dln-the-debridge-liquidity-network-protocol/protocol-specs/deterministic-order-id.md new file mode 100644 index 00000000..6b2e68fb --- /dev/null +++ b/docs/dln-the-debridge-liquidity-network-protocol/protocol-specs/deterministic-order-id.md @@ -0,0 +1,35 @@ +# Deterministic order ID + +An order placed onto DLN is identified by a deterministic `keccak256` hash derived from an array of bytes that contains all the properties of the order. The array is designed to be cross-chain compatible, so the smart contracts residing on both EVM and Solana can easily reproduce it. The smart contracts implementing the DLN protocol usually accept all these properties as a single data struct and derive an `orderId` programmatically to guarantee the order with the proper `orderId` is being managed. + +To get the deterministic `orderId` of the order, the array of bytes should contain its following properties: + +{% code fullWidth="true" %} +```sh +| Bytes | Bits | Field | +| ----- | ---- | ---------------------------------------------------- | +| 8 | 64 | Salt | +| 1 | 8 | Maker Src Address Size (!=0) | +| N | 8*N | Maker Src Address | +| 32 | 256 | Give Chain Id | +| 1 | 8 | Give Token Address Size (!=0) | +| N | 8*N | Give Token Address | +| 32 | 256 | Give Amount | +| 32 | 256 | Take Chain Id | +| 1 | 8 | Take Token Address Size (!=0) | +| N | 8*N | Take Token Address | +| 32 | 256 | Take Amount | | +| 1 | 8 | Receiver Dst Address Size (!=0) | +| N | 8*N | Receiver Dst Address | +| 1 | 8 | Give Patch Authority Address Size (!=0) | +| N | 8*N | Give Patch Authority Address | +| 1 | 8 | Order Authority Address Dst Size (!=0) | +| N | 8*N | Order Authority Address Dst | +| 1 | 8 | Allowed Taker Dst Address Size | +| N | 8*N | * Allowed Taker Address Dst | +| 1 | 8 | Allowed Cancel Beneficiary Src Address Size | +| N | 8*N | * Allowed Cancel Beneficiary Address Src | +| 1 | 8 | Is Hook Presented 0x0 - Not, != 0x0 - Yes | +| 32 | 256 | * Hook Envelope Hash | +``` +{% endcode %} diff --git a/docs/dln-the-debridge-liquidity-network-protocol/protocol-specs/hook-data/README.md b/docs/dln-the-debridge-liquidity-network-protocol/protocol-specs/hook-data/README.md new file mode 100644 index 00000000..4e9eb6f4 --- /dev/null +++ b/docs/dln-the-debridge-liquidity-network-protocol/protocol-specs/hook-data/README.md @@ -0,0 +1,15 @@ +# Hook data + +Hooks are on-chain actions that can be optionally attached to an order upon its creation, and become the inseparable and cryptographically signed part of it, and are executed on the destination chain upon order fulfillment. An action can perform actions of any complexity, including operations on the outcome of the order. + +Orders are identified by [deterministic order ID](../deterministic-order-id.md), and the hash of the hook's raw data is essentially a part of this ID (see the last two fields of the [deterministic order ID reference](../deterministic-order-id.md)), so once a user signs-off an order with a hook, he eventually makes a cryptographic confirmation that he is willing to sell input asset for output asset AND execute a specific action along with the output asset on the destination chain. + +The proper execution of the hook during order fulfillment is guaranteed by [`DlnDestination`](../../deployed-contracts.md) — the DLN smart contract responsible for filling orders, which ensures that the given hook along with other properties of the order actually match the given order ID. This means that trying to spoof a hook would lead to a different ID of a non-existent order, so the solver is compelled to pass a specific hook data, otherwise they would fill the non-existent order and eventually lose given funds forever. + +The raw data itself is not generalized, which allows the implementations of the `DlnDestination` smart contract for different blockchain engines impose different and platform-specific requirements. + +Further reading: + +* [anatomy-of-a-hook-for-the-evm-based-chains.md](anatomy-of-a-hook-for-the-evm-based-chains.md "mention") +* [anatomy-of-a-hook-for-solana.md](anatomy-of-a-hook-for-solana.md "mention") + diff --git a/docs/dln-the-debridge-liquidity-network-protocol/protocol-specs/hook-data/anatomy-of-a-hook-for-solana.md b/docs/dln-the-debridge-liquidity-network-protocol/protocol-specs/hook-data/anatomy-of-a-hook-for-solana.md new file mode 100644 index 00000000..c2b42a2e --- /dev/null +++ b/docs/dln-the-debridge-liquidity-network-protocol/protocol-specs/hook-data/anatomy-of-a-hook-for-solana.md @@ -0,0 +1,5 @@ +# Anatomy of a Hook for Solana + +The hook raw data intended for Solana is represented as a versioned transaction with one or more instructions. Thus, only **non-atomic** **success-required hooks** are supported. + +To craft a proper versioned transaction, use the guide: [creating-hook-data-for-solana.md](../../interacting-with-the-api/integrating-dln-hooks/creating-hook-data-for-solana.md "mention") diff --git a/docs/dln-the-debridge-liquidity-network-protocol/protocol-specs/hook-data/anatomy-of-a-hook-for-the-evm-based-chains.md b/docs/dln-the-debridge-liquidity-network-protocol/protocol-specs/hook-data/anatomy-of-a-hook-for-the-evm-based-chains.md new file mode 100644 index 00000000..ab4183d2 --- /dev/null +++ b/docs/dln-the-debridge-liquidity-network-protocol/protocol-specs/hook-data/anatomy-of-a-hook-for-the-evm-based-chains.md @@ -0,0 +1,150 @@ +# Anatomy of a Hook for the EVM-based chains + +During order fulfillment the `DlnDestination` smart contract performs standard routine procedures, including checking that the order is still open (neither Fulfilled nor Cancelled) and that the requested amount of tokens were successfully pulled from the solver. Finally, it transfers the requested amount of tokens to the order's recipient address, OR — if the hook raw data is provided — to the `DlnExternalCallAdapter` hook engine smart contract address (accessible via `DlnDestination.externalCallAdapter`), immediately invoking it for hook handling. + +`DlnExternalCallAdapter` is responsible for proper hook raw data decoding, hook execution, and guaranties that hook behavior conforms given properties. + +### Hook data V1 layout + +`DlnExternalCallAdapter` expects the hook raw data (called `externalCallEnvelope`) to be an concatenation of two `encodePacked`'d data types, specifically: + +1. first byte: `uint8 envelopeVersion` +2. subsequent bytes: `bytes envelopeData` + +The `envelopeVersion` determines which data structure was used to encode the `envelopeData`, + +#### Envelope v1 + +The `envelopeVersion=1` is currently the only available version, and its corresponding structure for the `envelopeData` is `HookDataV1` as follows: + +```solidity +struct HookDataV1 { + // Address that will receive order outcome if the hook gets reverted. + // Mandatory for optional-success hooks + address fallbackAddress; + + // The address of a smart contract that acts as a hook target. + // Must implement the IExternalCallExecutor interface + address target; + + // Optional reward to pay to a solver who executes the hook + // Reward is being cut off the order outcome. + // Reasonable (but not mandatory) for non-atomic hooks + uint160 reward; + + // False: atomic hook + // True: non-atomic hook + bool isNonAtomic; + + // False: optional-success hook + // True: success-required hooks + bool isSuccessRequired; + + // Arbitrary data to be passed to the target during the call + bytes targetPayload; +} +``` + +`HookDataV1.target` defines a target smart contract address that would get called by the `DlnExternalCallAdapter` hook engine smart. The target smart contract MUST implement the `IExternalCallExecutor` interface with only two functions: `onEtherReceived()` and `onERC20Received()` — that get called (along with order details and the payload) right after the native or ERC-20 token got transferred to it: + +
+ +The IExternalCallExecutor interface + +```solidity +interface IExternalCallExecutor { + /** + * @notice Handles the receipt of Ether to the contract, then validates and executes a function call. + * @dev Only callable by the adapter. This function decodes the payload to extract execution data. + * If the function specified in the callData is prohibited, or the recipient contract is zero, + * all Ether is transferred to the fallback address. + * Otherwise, it attempts to execute the function call. Any remaining Ether is then transferred to the fallback address. + * @param _orderId The ID of the order that triggered this function. + * @param _fallbackAddress The address to receive any unspent Ether. + * @param _payload The encoded data containing the execution data. + * @return callSucceeded A boolean indicating whether the call was successful. + * @return callResult The data returned from the call. + */ + function onEtherReceived( + bytes32 _orderId, + address _fallbackAddress, + bytes memory _payload + ) external payable returns (bool callSucceeded, bytes memory callResult); + + /** + * @notice Handles the receipt of ERC20 tokens, validates and executes a function call. + * @dev Only callable by the adapter. This function decodes the payload to extract execution data. + * If the function specified in the callData is prohibited, or the recipient contract is zero, + * all received tokens are transferred to the fallback address. + * Otherwise, it attempts to execute the function call. Any remaining tokens are then transferred to the fallback address. + * @param _orderId The ID of the order that triggered this function. + * @param _token The address of the ERC20 token that was transferred. + * @param _transferredAmount The amount of tokens transferred. + * @param _fallbackAddress The address to receive any unspent tokens. + * @param _payload The encoded data containing the execution data. + * @return callSucceeded A boolean indicating whether the call was successful. + * @return callResult The data returned from the call. + */ + function onERC20Received( + bytes32 _orderId, + address _token, + uint256 _transferredAmount, + address _fallbackAddress, + bytes memory _payload + ) external returns (bool callSucceeded, bytes memory callResult); +} + +``` + +
+ +For orders buying ERC-20 token, the `DlnExternalCallAdapter` first transfers the token's amount to the hook's `target`, then invokes its `onERC20Received()` method: + +```solidity +IERC20(order.takeToken).safeTransfer(hook.target, order.takeAmount); +IExternalCallExecutor(hook).onERC20Received(..., hook.targetPayload); +``` + +For orders buying native blockchain currency (ether, etc), the `DlnExternalCallAdapter` invokes the hook's `target.onEtherReceived()` method along with the amount of native currency as a `msg.value`: + +```solidity +IExternalCallExecutor(hook).onEtherReceived{ value: order.takeAmount }(..., hook.targetPayload); +``` + +### Universal Hook + +One the common ways to build cross-chain interactions is to make calls to arbitrary existing contracts, without the need to introduce custom intermediaries (smart contracts that act as hooks). To facilitate this need, we've build a default Universal Hook — a pre-deployed implementation of the `IExternalCallExecutor` and a part of the DLN deployment — that can act as a hook's `target` and is designed to transparently execute arbitrary transaction calls bypassed through its `targetPayload`. + +To reuse this hook, `HookDataV1`'s `target` must be set to `address(0)` (this would tell the `DlnExternalCallAdapter` hook engine to switch to the universal hook as a default hook implementation), and `HookDataV1`'s `targetPayload` must represent the following encoded data struct: + +```solidity +struct UniversalHookPayload { + address to; + uint32 txGas; + bytes callData; +} +``` + +When called, the universal hook would makes a `CALL` to the given `to` address using the given `callData`. + +The transfer of native blockchain currency is performed during the call itself: + +``` +payload.to.call{ gas: payload.txGas, value: order.takeAmount }(payload.callData); +``` + +Mind that ERC-20 token transfer during transaction call made by the Universal hook behaves differently: the universal hook does not transfer the token to the `to` address (like the `DlnExternalCallAdapter` does when calling a hook's `target`), but sets a temporary allowance instead before making a call, and reverts it back after the call: + +```solidity +IERC20(order.takeToken).approve(payload.to, order.takeAmount); +// payload.to must pull the tokens from the caller, using the size of the +// allowance as a reference amount +payload.to.call{ gas: payload.txGas }(payload.callData); +IERC20(order.takeToken).approve(payload.to, 0); + +// the remainder not consumed by the payload.to is transferred to the fallback address +``` + +If the `payload.to` had pulled less amount than the order's outcome, the remainder is transferred to the given fallback address automatically. + +If the call to the `payload.to` target gets reverted, the execution bubbles up to the `DlnExternalCallAdapter` hook engine who handles this failure according to the hook's properties (revert entire call if the hook is a success-required hook; gracefully ignore the failure if the hook is a success-optional hook).