BeanstalkFarms/Beanstalk

RFC: Tractor

Opened this issue · 0 comments

RFC: Tractor

Authors

Brendan Sanderson, Funderberker, Ben Weintraub, Brean, Guy

Summary

  • Tractor is a peer-to-peer transaction marketplace that allows third party operators to perform Beanstalk actions on behalf of a Farmer;
  • Blueprints are off-chain data structures that are EIP-712 signed to verify the intent of the publishing Farmer; and
  • Junctions are contracts that are contain functions that are used by Tractor orders.

Abstract

EVM users frequently demand peer-to-peer transactions. Existing peer-to-peer transaction marketplaces (such as Seaport) are limited in the scope of the functionality that they support. Existing generalized function call systems (smart contract accounts, Depot + Pipeline) only support the use of assets from the sender of the transaction. Tractor introduces a generalized function call peer-to-peer transaction marketplace.

Tractor

Blueprints allow Farmers to define sequences of on-chain function calls, which can be executed permissionlessly by other Farmers known as Tractor Operators.

Farmers Publish and Destroy Blueprints. Blueprints are EIP-712 signed payloads containing a Publisher, an Advanced Farm function call containing an arbitrary sequence of internal function calls, a set of copy instructions that define how to interpret operator calldata, expiry parameters and the EIP-712 signature from the Publisher. Any Tractor Operator can execute any Blueprint with any calldata at anytime through the tractor(...) function provided that the Blueprint does not revert.

Junctions allow Farmers to define Blueprints that will fail under a predefined set of conditions, such as balance limits, price thresholds, etc.

Examples

A Farmer creates a Blueprint for an Operator to Plant on their behalf anytime they have more than 100 Plantable Seeds and will pay the caller 1 Earned Bean.

A Farmer creates a Blueprint for an Operator to Rinse and Deposit on their behalf anytime they have more than 200 Rinsable Sprouts and will pay the caller 5 USDC.

A Farmer creates a Blueprint for an Operator to Convert their urBEANETH to urBEAN on their behalf anytime the Bean price is below $0.96 with no tip (the Operator just has to pay gas).

Rollout

Tractor is an on-chain primitive. Supporting markets on top of Tractor requires significant off-chain development in the form of indexers, middleware, and frontends. At the time of Tractor deployment, the goal is to first support Automated Farming, i.e., creating and executing orders for Mow and Plant with tips paid in ERC-20 tokens from the Publisher's Farm or Circulating balances.

Later the goal is to support a Convert order book on top of Tractor.

Specification

Blueprints

Blueprints are off-chain data structures that are EIP-712 signed to verify publisher intent. Each Blueprint contains an arbitrary sequence of internal and external function calls wrapped into an AdvancedFarm call and to be executed through the Tractor Facet. Any properly signed Blueprint can be executed through Tractor given:

  1. startTime < block.timestamp < endTime;
  2. blueprintNonce[nonce] < maxNonce; and
  3. the advancedFarm function call does not revert (Publishers can encode logic checks that revert under arbitrary conditions)

Blueprints are defined by the following struct:

struct Blueprint {
    address publisher;
    bytes data;
    bytes32[] operatorPasteInstrs;
    uint256 maxNonce;
    uint256 startTime;
    uint256 endTime;
}

Blueprints are wrapped in a Requisition, which contains the Blueprint hash and the Publisher's signature of the hash.

struct Requisition {
    Blueprint blueprint;
    bytes32 blueprintHash;
    bytes signature;
}

Where:

  1. publisher is the account that published the Blueprint;
  2. data is bytes that decode into (AdvancedFarmCall[]);
  3. operatorPasteInstrs are a set of instructions that define how operator-defiend data is injected into the AdvancedFarmCalls of the data;
  4. maxNonce The maximum # of times a Blueprint can be executed;
  5. startTime The timestamp at which the Blueprint can start to be executed;
  6. endTime The timestamp at which the Blueprint can no longer be executed;
  7. blueprintHash The keccak256 hash of the populated Blueprint struct; and
  8. signature The Publisher's EIP-712 signature of the Blueprint Hash.

Blueprint Hash

Blueprint Hashes are unique identifiers for Blueprints. Blueprint Hashes are used to track the nonce of a Blueprint and used in the signing process. A Blueprint Hash must be hashed following the EIP-712 standard. Beanstalk providesa public helper function getBlueprintHash(Blueprint blueprint).

Publish Blueprint

Requisitions do not need to be written on-chain. They can be provided to operators off-chain and verified via the signature. However, Tractor offers a system to publicly publish any Requisition via the PublishRequisition function, which emits an event containing the Requisition.

function function publishRequisition(LibTractor.Requisition calldata requisition) external;

event PublishRequisition(LibTractor.Requisition requisition);

Destroy Blueprint

Blueprints can be canceled at anytime by the Blueprint Publisher by calling cancelBlueprint. This sets the nonce to uint256.max, rendering the Blueprint unusable.

function cancelBlueprint(LibTractor.Requisition calldata requisition) external;

event CancelBlueprint(bytes32 blueprintHash);

Tractor

function tractor(LibTractor.Requisition calldata requisition, bytes memory operatorData) external returns (bytes[] memory results);

event Tractor(address indexed operator, bytes32 blueprintHash);

Tractor executes a Blueprint. Executing a Blueprint does the following:

  1. Verifies the Blueprint signature is correct;
  2. Checks startTime < block.timestamp < endTime;
  3. Checks blueprintNonce[blueprintHash] < maxNonce;
  4. Increments blueprintNonce[blueprintHash];
  5. Modifies blueprint.data using the operatorPasteInstrs and operatorData;
  6. Executes advancedFarm using data; and
  7. Emits a Tractor event.

Tractor Storage

Instead of adding new state variables to AppStorage, create a TractorStorage library where TractorStorage is loaded at slot keccak256("diamond.storage.tractor") (store as a constant).

struct TractorStorage {
    mapping(bytes32 => uint256) blueprintNonce;
    mapping(address => mapping(bytes32 => uint256)) blueprintCounters;
    address activePublisher;
}

Tractor Counters

Blueprints can utilize arbitrary counters to track and limit use. These are stored in TractorStorage and writes are restricted based on the Publisher's address. Blueprints using counters are expected to generate sufficiently random counter IDs to avoid collisions with a Publisher's other counters.

Active Publisher

Tractor performs actions on behalf of the Publisher of a Blueprint that that is executed by Tractor. Rather than using msg.sender to determine the account to act on, the active Publisher address is used.

Junctions

TBD.