From 74685189ac37c1925d6c3d9fa0a49303f471d8ea Mon Sep 17 00:00:00 2001 From: Yin Jiaquan Date: Tue, 25 Oct 2022 23:38:39 +0800 Subject: [PATCH 1/2] Update conveyor docs --- docs/conveyor/appendix.md | 23 ++++ docs/conveyor/conveyor-base.md | 64 ++++++++++ docs/conveyor/conveyor-sdk.md | 100 +++++++++++++++ docs/conveyor/forwarder.md | 115 ++++++++++++++++++ .../automata-conveyor-controller.md | 0 .../conveyorv1/automata-conveyor-gtoken.md | 0 .../conveyorv1/automata-conveyor-overview.md | 8 +- .../{ => legacy}/conveyorv1/fee-contract.md | 0 docs/conveyor/{ => legacy}/conveyorv1/fee.md | 0 .../{ => legacy}/conveyorv2/design.md | 4 +- docs/conveyor/legacy/legacy.md | 17 +++ docs/conveyor/overview.md | 11 +- mkdocs.yml | 22 ++-- 13 files changed, 343 insertions(+), 21 deletions(-) create mode 100644 docs/conveyor/appendix.md create mode 100644 docs/conveyor/conveyor-base.md create mode 100644 docs/conveyor/conveyor-sdk.md create mode 100644 docs/conveyor/forwarder.md rename docs/conveyor/{ => legacy}/conveyorv1/automata-conveyor-controller.md (100%) rename docs/conveyor/{ => legacy}/conveyorv1/automata-conveyor-gtoken.md (100%) rename docs/conveyor/{ => legacy}/conveyorv1/automata-conveyor-overview.md (78%) rename docs/conveyor/{ => legacy}/conveyorv1/fee-contract.md (100%) rename docs/conveyor/{ => legacy}/conveyorv1/fee.md (100%) rename docs/conveyor/{ => legacy}/conveyorv2/design.md (98%) create mode 100644 docs/conveyor/legacy/legacy.md diff --git a/docs/conveyor/appendix.md b/docs/conveyor/appendix.md new file mode 100644 index 00000000..a9aba9b7 --- /dev/null +++ b/docs/conveyor/appendix.md @@ -0,0 +1,23 @@ +# Appendix + +## `_extractData()` + +### Rationale + +Users may request and consume additional data generated by a trusted hardware. + +To request for a data, users need to specify the type of data to consume using an integer denoted cateogory. For instance, users need to assign `extendParamData = 1` to request for a VRF generated random number, which can be extracted in the target smart contract. + +This section goes into the technical detail of the data extraction process. + +### Specification + +The requested param data is being appended after the `metatx.data` followed by the 20-byte address `metatx.from`. This entire data is then being forwarded to the target contract to execute functions. For the target contract to retrieve the given value from the data, inline assembly is required to slice the `extendParamData` portion out of `msg.data`. + +The data to be forwarded to the base contract follows the structure as described by the table below: + +| `metatx.data` | `extendParamData` | length of `extendParamData` | `metatx.from` | +|---|---|---|---| +| < dynamically sized > | < dynamically sized > | < 32 bytes > | < 20 bytes > | + +**⚠ NOTE:** The data is being fed to the consumer smart contract as a dynamically sized `bytes` array. The consumer is responsible for decoding the raw data to its appropriate type. Using the same example with random numbers as above, the consumer should decode the raw bytes data to a `uint256` value. diff --git a/docs/conveyor/conveyor-base.md b/docs/conveyor/conveyor-base.md new file mode 100644 index 00000000..8f4a31be --- /dev/null +++ b/docs/conveyor/conveyor-base.md @@ -0,0 +1,64 @@ +# ConveyorBase + +The ConveyorBase Contract includes administrative controls on the implementation targets, where owners can do the following: + +- Enables/Disables Conveyor protection +- Sets a trusted forwarder address + +With the additional support of Conveyor Extensions, the Geode Oracle returned data can be extracted by using the `_extractData()` method. The implementation contract is responsible for decoding the data into its desired type using the `abi.decode()` method. + +## `constructor()` + +This contract is to be initialized by providing the Forwarder address. + +```solidity +constructor(address _forwarder) +``` + +|Param|Type|Description| +|---|---|---| +|`_forwarder` | address | The address of the trusted forwarder contract | + +## `setForwarder()` + +Changes the trusted Forwarder address. **Requires: Ownership privilege** + +```solidity +function setForwarder(address _forwarder) external onlyOwner +``` + +|Param|Type|Description| +|---|---|---| +|`_forwarder` | address | The address of the trusted forwarder contract | + +## `isTrustedForwarder` + +Checks if the provided address is a trusted Forwarder + +```solidity +function isTrustedForwarder(address _forwarder) public +``` + +|Param|Type|Description| +|---|---|---| +|`_forwarder` | address | Address to be verified | + +## `onlyConveyor` + +A modifier that restricts a method, such that its caller can only originate from the `Forwarder` address + +## `enableConveyorProtection` + +Enables the `conveyorIsEnabled` state. This effectively turns any methods with the `onlyConveyor` modifier to benefit from MEV protection. + +## `disableConveyorProtection` + +Disables the `conveyorIsEnabled` state. Allowing users to directly executing methods even with the `onlyConveyor` modifier. + +## `_msgSender()` + +You **MUST** use this method to get the sender's address. Avoid using the default `msg.sender` value. + +## `_extractData()` + +This low-level method extracts the Conveyor Extension data from `msg.data`. Check out the [Appendix](./appendix.md) section for technical details. diff --git a/docs/conveyor/conveyor-sdk.md b/docs/conveyor/conveyor-sdk.md new file mode 100644 index 00000000..52f430c7 --- /dev/null +++ b/docs/conveyor/conveyor-sdk.md @@ -0,0 +1,100 @@ +# ConveyorSDK + +The Conveyor SDK is a JavaScript library for developers to seamlessly interact with smart contracts that benefit from MEV protection with Conveyor. + +Long story short, users submit meta-transactions to their contracts by following the three-step process below: + +1. Instantiate the module and set your Web3 provider +2. Approve the Forwarder contract to collect fee payment with your choice of ERC20 tokens*. +3. Submit a request and provide the necessary parameters. + +*ERC20 token must be enlisted on CoinGecko as a measure to determine market value, otherwise the transaction may be rejected for unsupported fee token. + +--- + +## Specification + +The main module has the following functions built-in: + +- `erc20ApproveForwarder`- Sets an allowance for the Forwarder contract to transfer ERC20 tokens for fee payment. +- `submitConveyorTransaction` - Constructs the request body to the Geode relayer to interact with contracts that are protected by Conveyor. +- `submitTransaction` - Submits a regular transaction directly to the target address. This can be used to execute methods that do not have the `onlyConveyor` modifier or to contracts that have disabled Conveyor protection. +- `fetchConveyorStatus` - detects whether Conveyor protection is enabled for the given target contract. +- `toggleConveyorProtection` - enables/disables Conveyor protection on the given target contract. + +--- +## Configurable Variables + +The canonical `RELAYER_ENDPOINT_URL` and `FORWARDER_ADDRESS` are defined in the SDK. This is because most projects are likely using the same relayer and Forwarder contract. If your project were to fall in an exceptional circumstance which a dedicated relayer and forwarder are provided, you may simply overwrite the variables, by passing the following environmental variables + +``` +FORWARDER +RELAYER +``` +In JavaScript, it would look something like this: + +```javascript +process.env.RELAYER = +process.env.FORWARDER = 0x +``` + +--- + +## Quick Guide + +As mentioned previously, submitting a transaction using Conveyor only requires three steps. + +**Step 1: Instantiate the module and set your Web3 provider** + +```javascript +import { Conveyor } from '@conveyor/sdk'; +const web3 = window.ethereum; // Metamask +const conveyor = new Conveyor(web3); +``` + +**Step 2: Approve the Forwarder contract for collecting fees** + +:warning: *This step must be performed before moving on to the next step. Otherwise, your transaction may fail due to zero allowance* + +```javascript +await erc20ApproveForwarder( + "100000000", // 100 USDC + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC address on ETH +) +``` + +In the above example, the user is allocating 100 USDC of allowance to the Forwarder contract. + + +**Step 3: Submit the transaction** + +The `submitMetaTransaction()` function requires the following parameters: + +| Params | Type | Description | +| -------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `feeToken` | string | **REQUIRED:** The fee token address | +| `gasLimit` | string | **REQUIRED:** The gas limit - this value is used for calculating the maximum fee token amount, it may not be the actual gas limit provided to the transaction | +| `gasPrice` | string | **REQUIRED:** The gas price - this value is used for calculating the maximum fee token amount, it may not be the actual gas price provided to the transaction | +| `duration` | string | **REQUIRED:** The duration in seconds until the meta-txn expires | +| `domainName` | string | **REQUIRED:** The EIP712 domain name | +| `useOraclePriceFeed` | boolean | **REQUIRED:** True: use an oracle price feed as a source to fetch fee token price, false: otherwise | +| `extendCategories` | Array | **REQUIRED:** An array of numeric categories that maps to the request extension type. Pass the `[0]` value to omit extensions. To request an N-amount of extensions, provide an array of N-size with their corresponding categories. For example, to request x2 randomly generated numbers, input `[1,1]` | +| `fromAddress` | string | **REQUIRED:** This can be an EOA address or a smart contract address to support EIP 1271 Signature Verification. If an EOA address is provided, it must match the signing address, otherwise the transaction will fail. | +| `targetAddress` | string | **REQUIRED:** The address of the implementation contract | +| `targetAbi` | string | **REQUIRED:** The abi of the implementation contract | +| `methodName` | string | **REQUIRED:** The name of the method to invoke | +| `params` | Array | **OPTIONAL:** The method parameters to be stored as an array | + +The `submitTransaction()` method can be invoked to execute functions that do not have the `onlyConveyor` modifier. + + +--- + +# Available extensions + +This section describes the category index that are passed into the `extendCategories` parameter to get external data that can be useful for your target contracts. + +| Category | Description | +|---|---| +| 0 | No extension | +| 1 | Random Number Generated by A Trusted Hardware | diff --git a/docs/conveyor/forwarder.md b/docs/conveyor/forwarder.md new file mode 100644 index 00000000..ac5604a5 --- /dev/null +++ b/docs/conveyor/forwarder.md @@ -0,0 +1,115 @@ +# ConveyorForwarder + +The Forwarder contract enables meta-transaction executions, this contract ensures the relayer account is compensated for gas that is charged upon the users to be paid in ERC20 tokens. The Forwarder contract is also responsible for EIP712 Signature verification to validate the origin sender address, before forwarding the transaction to the implementation contract. + +![erc20-forwarder.png](/assets/conveyorv2/forwarder_flow.png) + +## **`setConstantFee()`** + +```solidity +function setConstantFee(uint256 _newConstantFee) public onlyOwner +``` + +Updates the `constantFee` value, defaults at 21000. Requires `owner()` privilege. + +|Parameter|Type| | +|---|---|---| +| _newConstantFee | `uint256` | constant gas limit value | + +## **`setTransferFee()`** + +```solidity +function setTransferFee(uint256 _newTransferFee) public onlyOwner +``` + +Updates the ERC20 transfer gas limit, the `transferFee` value, defaults at 65000. Requires `owner()` privilege. + +|Parameter|Type| | +|---|---|---| +| _newTransfeFee | `uint256` | ERC20 transfer limit value | + +## **`setRelayer()`** + +```solidity +function setRelayer(address _relayer, bool _trusted) public onlyOwner +``` + +Assigns or revokes relayer role. Requires `owner()` privilege. + +|Parameter|Type| | +|---|---|---| +| _relayer | `address` | Relayer address | +| _trusted | `bool` | True: grant authorization; false: revoke authorization | + +## **`MetaTransaction`** + +```solidity +struct MetaTransaction { + address from; + address to; + address feeToken; + bool useOraclePriceFeed; + uint256 maxTokenAmount; + uint256 deadline; + uint256 nonce; + bytes data; + uint256[] extendCategories; +} +``` + +|Parameter|Type| | +|---|---|---| +| from | `address` | the user's address | +| to | `address` | the recipient contract address | +| feeToken | `address` | The address of the ERC20 fee token | +| useOraclePriceFeed | `bool` | Users can opt for getting token price from an on chain oracle | +| maxAmountToken | `uint256` | The maximum amount of fee tokens that the users are willing to pay for the meta-tx | +| deadline | `uint256` | Meta-transactions that are executed past the deadline will be reverted | +| nonce | `uint256` | Replay protection | +| data | `bytes` | The encoded function data that is to be executed | +| extendCategories | `uint256[]` | An array of numeric categories mapping the request extension type | + + +## **`executeMetaTxV2()`** + +```solidity +function executeMetaTxV2( + ConveyorTypes.MetaTransaction memory metatx, + string memory domainName, + bool relayerChargeEnabled, + uint256 tokenPricePerNativeToken, + SignatureSignerType signerType, + bytes memory sig, + bytes memory extendParamData +) +``` + +This function executes the encoded function data, as defined in `MetaTransaction.data`. It calculates the amount of gas consumption within the enveloped function call and charges the user the fee amount that is based on the token/native (e.g. ERC20/ETH on Ethereum, ERC20/BNB on BSC etc.) price `tokenPricePerNativeToken`. This value is given by the Geode (or from an on chain oracle if applicable). There are certain situations that could cause the transaction to revert, which the relayers will not be compensated for gas fee. The point of reverts are: + +- Insufficient fee token balance +- Insufficient `maxAmountToken` +- Invalid EIP 712 signature +- Out of gas error + +|Parameter|Type| | +|---|---|---| +| metatx | `MetaTransaction` | The meta-transaction body | +| domainName | `string` | The EIP712 domain name | +| tokenPricePerNativeToken | `uint256` | ERC20 token price per native EVM token (e.g. ETH on Ethereum, Matic on Polygon PoS etc.) | +| SignatureType | `SignatureSignerType` | Signer Type, EOA or CONTRACT ([EIP 1271](https://eips.ethereum.org/EIPS/eip-1271) Compliant) | +| sig | `SIGNATURE_TYPR` | The user's EIP712 signature | +| extendParamData | `bytes` | The returned Extension data | + +## **`MetaStatus`** + +```solidity +event MetaStatus(address sender, bool success, string error) +``` + +The `executeMetaTxV2` logs the event to indicate the status of the meta-tx. Execution failure within the meta-tx will not cause the function to revert. To view the error logs of a meta transaction, the client can simply listen for the `error` message emitted by this event. + +|Parameter|Type| | +|---|---|---| +| sender | `address` | user address | +| success | `bool` | status of the meta-tx | +| error | `string` | reason of a reverted meta-tx | diff --git a/docs/conveyor/conveyorv1/automata-conveyor-controller.md b/docs/conveyor/legacy/conveyorv1/automata-conveyor-controller.md similarity index 100% rename from docs/conveyor/conveyorv1/automata-conveyor-controller.md rename to docs/conveyor/legacy/conveyorv1/automata-conveyor-controller.md diff --git a/docs/conveyor/conveyorv1/automata-conveyor-gtoken.md b/docs/conveyor/legacy/conveyorv1/automata-conveyor-gtoken.md similarity index 100% rename from docs/conveyor/conveyorv1/automata-conveyor-gtoken.md rename to docs/conveyor/legacy/conveyorv1/automata-conveyor-gtoken.md diff --git a/docs/conveyor/conveyorv1/automata-conveyor-overview.md b/docs/conveyor/legacy/conveyorv1/automata-conveyor-overview.md similarity index 78% rename from docs/conveyor/conveyorv1/automata-conveyor-overview.md rename to docs/conveyor/legacy/conveyorv1/automata-conveyor-overview.md index fbfe629c..666031cb 100644 --- a/docs/conveyor/conveyorv1/automata-conveyor-overview.md +++ b/docs/conveyor/legacy/conveyorv1/automata-conveyor-overview.md @@ -1,17 +1,11 @@ # Overview -Automata Conveyor is an anti-front-running service that ingests and outputs transactions in a determined order. - ---- - -Automata Conveyor currently has the following components: +ConveyorV1 has the following components: - **gToken smart contract**: inherits the ERC20 contract with modified function methods that must be invoked by trusted Geode-provided addresses. - **Controller smart contract**: interacts with the users and facilitates the transfer of gTokens. External calls to any functions in the contract from any addresses other than the trusted relayers will be reverted. -- **Automata Conciliator**: TBA - The following is an overview of the architecture. ![Protocol-overview](/assets/conveyorv1/conveyor-protocol-flowchart.svg) diff --git a/docs/conveyor/conveyorv1/fee-contract.md b/docs/conveyor/legacy/conveyorv1/fee-contract.md similarity index 100% rename from docs/conveyor/conveyorv1/fee-contract.md rename to docs/conveyor/legacy/conveyorv1/fee-contract.md diff --git a/docs/conveyor/conveyorv1/fee.md b/docs/conveyor/legacy/conveyorv1/fee.md similarity index 100% rename from docs/conveyor/conveyorv1/fee.md rename to docs/conveyor/legacy/conveyorv1/fee.md diff --git a/docs/conveyor/conveyorv2/design.md b/docs/conveyor/legacy/conveyorv2/design.md similarity index 98% rename from docs/conveyor/conveyorv2/design.md rename to docs/conveyor/legacy/conveyorv2/design.md index 2a6d885c..61599bff 100644 --- a/docs/conveyor/conveyorv2/design.md +++ b/docs/conveyor/legacy/conveyorv2/design.md @@ -2,8 +2,8 @@ ## Summary -ConveyorV2 is an upgrade of Automata's ConveyorV1 to support a better user -experience. +The ConveyorV2 smart contracts are the backbone of XATA. Click [here](../../../xata/introduction.md) to learn more about the product. + Tokens that are traded in the ConveyorV2 liquidity pools enjoy the MEV protection and transaction ordering enforcement capabilities without the need to wrap existing ERC20 tokens into gTokens. Similar with ConveyorV1 (formerly known as GTokens), users must sign an EIP712 message to authorize Geode to submit a transaction on the user's behalf. Users would still enjoy the benefit of gasless trading, meaning they are not required to hold native tokens like MATIC or BNB to pay for gas. diff --git a/docs/conveyor/legacy/legacy.md b/docs/conveyor/legacy/legacy.md new file mode 100644 index 00000000..64102697 --- /dev/null +++ b/docs/conveyor/legacy/legacy.md @@ -0,0 +1,17 @@ +# Legacy Contracts + +## ConveyorV1: gTokens (Deprecated) + +The initial development of Conveyor involves wrapping a tradable token, such as USDC, into anti-front running tokens known as gTokens. + +It also included a companion contract, known as the Controller, which ensured that the transfer of gTokens can only be done by a trusted relayer. + +gTokens are now deprecated, in favor of XATA. + +## ConveyorV2: XATA + +The second iteration of Conveyor uses a Forwarder approach, which laid down the foundation for the latest iteration of Conveyor contracts, also known as Generic Conveyor. + +In the case of XATA, we cloned the `UniswapV2` contracts and restrict the caller, such that it must only originate from authorized relayers. + +Eventually in the later phase, we introduced Generic Conveyor to provide a general purpose transaction-ordering solution. diff --git a/docs/conveyor/overview.md b/docs/conveyor/overview.md index c1130015..34d1df52 100644 --- a/docs/conveyor/overview.md +++ b/docs/conveyor/overview.md @@ -6,9 +6,12 @@ Automata Conveyor is a MEV Minimization solution that enforces fair ordering of Because blockchains are written by consensus and the content of each block is chosen by block producers - miners in PoW and validators in PoS systems - there is room and frankly, great incentive, for them to profit by front-running, back-running, sandwiching and generally exploiting transactions in their block. This is what is referred to as Maximal Extractable Value (MEV). -## How it works +For example, a DEX can choose to accept either -When transactions are fed into Conveyor, the service ingest and outputs incoming transactions by a FIFO order. This means block producers cannot: + - Ordered transactions from Automata’s Conveyor which is free from transaction reordering and other front-running transactions + - Other unordered transactions (which include front-running etc) that may negatively impact their users -- Inject new transactions into the Conveyor output. Any inserted transactions bypassing Conveyor is detectable by anyone because of signature mismatch. -- Delete ordered transactions. Transactions accepted by Conveyor are broadcasted throughout the network so transactions cannot be deleted unless all block producers are colluding and censoring the transactions at the same time. +Automata Conveyor contracts comprise the following components: + +- `ConveyorForwarder` +- A target contract that extends the `ConveyorBase` contract. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 77dcd327..6b7830a9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -80,14 +80,20 @@ nav: - Join as Validator: ./canarynet/node/run-validator.md - Conveyor: - Overview: ./conveyor/overview.md - - ConveyorV1: - - ./conveyor/conveyorv1/automata-conveyor-overview.md - - ./conveyor/conveyorv1/automata-conveyor-gtoken.md - - ./conveyor/conveyorv1/automata-conveyor-controller.md - - ./conveyor/conveyorv1/fee.md - - ./conveyor/conveyorv1/fee-contract.md - - ConveyorV2: - - ./conveyor/conveyorv2/design.md + - Forwarder: ./conveyor/forwarder.md + - ConveyorBase: ./conveyor/conveyor-base.md + - SDK: ./conveyor/conveyor-sdk.md + - Legacy: + - Summary: ./conveyor/legacy/legacy.md + - ConveyorV1 (Deprecated): + - ./conveyor/legacy/conveyorv1/automata-conveyor-overview.md + - ./conveyor/legacy/conveyorv1/automata-conveyor-gtoken.md + - ./conveyor/legacy/conveyorv1/automata-conveyor-controller.md + - ./conveyor/legacy/conveyorv1/fee.md + - ./conveyor/legacy/conveyorv1/fee-contract.md + - ConveyorV2: + - ./conveyor/legacy/conveyorv2/design.md + - Appendix: ./conveyor/appendix.md theme: name: material From 7855875d9641c534c8eb9d1af4e0d23f1669ccbf Mon Sep 17 00:00:00 2001 From: Yin Jiaquan Date: Wed, 26 Oct 2022 15:17:54 +0800 Subject: [PATCH 2/2] Add a dev guide --- docs/conveyor/guide.md | 27 +++++++++++++++++++++++++++ docs/conveyor/overview.md | 2 +- mkdocs.yml | 1 + 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 docs/conveyor/guide.md diff --git a/docs/conveyor/guide.md b/docs/conveyor/guide.md new file mode 100644 index 00000000..8ff0003f --- /dev/null +++ b/docs/conveyor/guide.md @@ -0,0 +1,27 @@ +# Developer guide + +If you are interested in Conveyor, feel free to contact us to have an internal round development. Here are the basic development procedures: + +1. Create your own smart contract based on the [ConveyorBase Contract](./conveyor-base.md), restrict the methods by using the `onlyConveyor` modifier to get the MEV protected benefit. + +2. Deploy your target smart contract to any testnet, and you need to contact us to deploy the forwarder contract and a dedicated relayer backend in this testnet too. + +3. Implement your own dApp by using [Conveyor SDK](./conveyor-sdk.md), specify the dedicated relayer url and forwarder address in previous step. + +4. Start the internal tests in the testnet, especially focus on the methods which use `onlyConveyor` modifier. + +5. If everything goes well during the testing, please contact us for the production deployment: + + * Automata team deploys a forwarder contract to the network where your dApp will be deployed. + + * Automata team deploys a dedicated relayer backend. + + * Automata team configures the relayer account to the forwarder contract. + + * The developer deploys the target contract in the same network. + + * The developer deploys the dApp with the conveyor-sdk, configuring the relayer url and forwarder address. + + * All deployments are done, make a final round test in the mainnet before publishing this dApp. + +If you meet any issues during your development or deployment process, contact us for help. diff --git a/docs/conveyor/overview.md b/docs/conveyor/overview.md index 34d1df52..d77f0908 100644 --- a/docs/conveyor/overview.md +++ b/docs/conveyor/overview.md @@ -1,4 +1,4 @@ -## Overview +# Overview ![](../assets/conveyor_updated.png){style="zoom:80%"} diff --git a/mkdocs.yml b/mkdocs.yml index 6b7830a9..7888cde5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -83,6 +83,7 @@ nav: - Forwarder: ./conveyor/forwarder.md - ConveyorBase: ./conveyor/conveyor-base.md - SDK: ./conveyor/conveyor-sdk.md + - Dev Guide: ./conveyor/guide.md - Legacy: - Summary: ./conveyor/legacy/legacy.md - ConveyorV1 (Deprecated):