BSIP: 72
Title: Tanks and Taps: A General Solution for Smart Contract Asset Handling
Authors: Nathan Hourt <[email protected]>
Status: Draft
Type: Protocol
Created: 2019-08-27
Discussion: https://github.com/bitshares/bsips/issues/178
This BSIP proposes the addition of novel, generally-applicable asset handling functionality to BitShares. These additions will support the development of a wide range of financial decentralized applications (dapps), advancing BitShares as an alternative platform to existing Turing Complete Smart Contract Platforms (TCSCPs) for dapp development. Although TCSCPs will continue to offer more flexible capabilities, the proposed infrastructure will offer a simple, ready-to-use interface to dapp developers which will facilitate more rapid development and a reduced time to market compared with generic Turing Complete platforms, which require dapps to define their own infrastructure. Furthermore, dapps utilizing the proposed infrastructure will share a consistent, easy-to-visualize asset handling model which will become increasingly familiar to users of dapps based on BitShares, giving them confidence when using BitShares dapps that they understand how their assets are being handled and what options are available to them under contract.
One of the main promises of blockchain technology is the processing and execution of "smart contracts," a formalized agreement between parties where the terms are evaluated and enforced automatically rather than relying on trusted intermediaries. Initially, there was Bitcoin, which offers a simple smart contract definition language allowing the transfer of digital tokens between cryptographic keys. Subsequently, the industry has created multiple Turing Complete Smart Contracting Platforms (TCSCPs), such as Ethereum and EOS, in order to provide more advanced smart contract definition languages which can specify any programmable algorithm as a smart contract.
BitShares was created as a financial services smart contracting platform, providing high-performance decentralized financial contracts including an asset exchange, stable-valued assets, recurring payments, and an advanced multi-signature named account system. This curated selection of contracts is developed and supported by a highly competent team of developers who are committed to the quality, security, and correctness of the supported smart contracts. These assurances of contract quality give BitShares an advantage over TCSCPs, where contracts are user-contributed with no expectation of testing or correctness standards; however, to date, neither the more flexible Turing Complete model nor the quality-assured Curated model have clearly succeeded, either in terms of developer support or user adoption.
This proposal is to implement within BitShares a general framework for smart contract asset handling, capable of supporting a great many decentralized applications (dapps) and real world smart contracts. The benefit of such a uniform asset handling framework, as opposed to the TCSCP approach of generally supporting any and all potential asset management designs, is twofold. Firstly, it provides, across all BitShares dapps, a simple and consistent representation of user funds, making it easy for users to understand and reason about the status of assets within the contract, and what options they have for moving assets through the contract. Secondly, specifying a particular structure for asset handling gives developers a clear path for implementing their dapp's asset management, reducing their task from the creation of a novel asset handling architecture for their specific dapp, to merely expressing their dapp's asset movement possibilities within an already-implemented and battle-tested framework.
With this asset handling infrastructure implemented, BitShares would enjoy significant advantages over TCSCPs for dapp development. A great deal of the complexity of designing and implementing dapps which handle user funds lies in the creation of secure mechanisms for holding funds within the contract, and the represention of these mechanisms to the user in a comprehensible fashion. By providing a simple and consistent framework for asset handling, BitShares offers developers a simpler and faster implementation path by providing sturdy and supported infrastructure for asset handling which provides users a familiar and consistent representation of the status of contract funds.
The proposed asset handling framework is a series of asset depositories called "tanks," which have one or more withdrawal mechanisms called "taps." Tanks are simple database objects which track a balance of asset contained within them. A tank has one or more taps on it, which allow withdrawing asset from the tank. Taps are locked such that only specific authorities can open them, and may have restrictions on when or why they can be opened or how much asset can flow through them. Taps are connected to other asset depositories, such as another tank, or an account, and when opened, the asset that flows through them are deposited there. More asset may be added to a tank at any time.
All tanks will be required to have at least one tap which is capable of draining the entire tank immediately. This tap can be thought of as an emergency release valve, and will typically have an authority requiring a supermajority approval of all contract parties. This tap will typically not be connected to any destination until and unless it is used. Its purpose is to handle scenarios where real-world conditions have gone outside the bounds of scenarios planned for under contract (for example, a contract party dies or is otherwise rendered incapable of completing the contract as negotiated), and it becomes necessary to renegotiate the contract mid-flight, potentially rewriting rules for how contract funds can be handled. In such emergent scenarios, it is imperative that the contract platform have a contingency that allows contract parties to reallocate funds based upon renegotiated terms, since the prior-negotiated rules of fund allocation may no longer be usable. In most cases, if the emergency tap is used, it will be connected to a new tank with newly negotiated taps immediately before it is opened, and all funds will be drained to the new tank.
Taps are somewhat more intricate than tanks, as they define the authorities, limitations, and requirements for opening them. First, a tap specifies an authority required to open it. Next, it specifies requirements to open it, such as a requirement for a written reason for the withdrawal, or an approval from a second authority. Finally, it specifies limitations as to how much asset can flow through it once opened.
The power of this model lies in its simplicity. It mimics how money moves in real-world contracts: a balance is locked up for a purpose in advance, when exactly how that balance will be spent is not yet known. As expenditures arise, they are paid out of the balance by an authorized party. Only certain kinds of expenditures can be paid from the balance, and in some cases, a justification or review is required. Eventually, the balance may need to be topped up to support further expenditures. It is anticipated that this model will be general enough to support many smart contract use cases.
A further advantage of this model is that it is easy to visualize, both the current state of contract funds, and the effects a transaction under consideration will have. This is crucial, as it allows the development of intuitive GUIs which represent the status of contract funds to users in a readily accessible fashion, giving them confidence that they understand how their money is being handled and how they can interact with it under terms of contract.
This proposal seeks to define five new types within the BitShares protocol, namely tank_object
, tap
, tap_requirement
, tank_attachment
, and sink
. A tank_object
is an object which contains a volume of asset. A tap
is an object attached to a tank_object
which is capable of releasing asset from the tank. A tap_requirement
is a static_variant
of various requirement types which can be attached to a tap
to impose limits and requirements on when and why a tap
can be opened, and how much asset can flow through it. A tank_attachment
is a static_variant
which may contain one of several structures to be stored on the tank to provide additional functionality for the tank or taps. Finally, a sink
is a static_variant
of various types which specify what shall be done with asset that has flowed through a tap
. A tank_object
has one or more tap
s; a tap
may contain zero or more tap_requirement
s and is optionally connected to a sink
. A tap
must be connected to a sink
before it can be opened.
When a tap
is opened, the amount of asset to release must be known. A tap
can be opened in one of two modes: to release a fixed amount of asset, or to release the maximum amount of asset available. In the former case, the amount to release is set in the transaction that opens the tap
. In the latter case, the amount to release is determined by the tap_requirement
s, or the amount of asset remaining in the tank, whichever is lower.
A tap_requirement
is attached to a tap
and locks the tap
until certain conditions are met. tap_requirement
s contain several parameters which are defined when the requirement is created, and may require arguments to be passed in the transaction when unlocking or opening the tap
. Some tap_requirement
s are stateful; requirement state is stored by the tank_object
. Initially, the following tap_requirement
types are specified:
immediate_flow_limit
Specifies a fixed maximum amount of asset that may flow through the tap in a given openingcumulative_flow_limit
Specifies a maximum amount of asset that may flow through the tap before it locks permanently (works in conjunction with anasset_flow_meter
)periodic_flow_limit
[Stateful] Specifies a maximum amount of asset that may flow through the tap before it locks until a predefined time period elapses (works in conjunction with anasset_flow_meter
)time_lock
Specifies time periods during which the tap is lockedminimum_tank_level
Specifies a minimum amount in the tank, such that the tap cannot drain the tank past that minimumreview_requirement
[Stateful] Specifies a reviewer authority which must approve requests to unlock the tapdocumentation_requirement
Requires documentation of the reason for opening the tap, such as a cost justification, without requiring review of this documentationdelay_requirement
[Stateful] Requires a delay before tap unlocks, with optional veto authority which can prevent tap from unlocking even after the delay expireshash_preimage_requirement
Requires a byte sequence which hashes to a predefined digest to unlock the tapticket_requirement
[Stateful] Requires a ticket (see code specification below) signed by a predefined key granting permission to release a limited volume of fundsexchange_requirement
[Stateful] Checks anasset_flow_meter
(see tank attachments below) and applies an exchange rate to calculate the maximum flow limit
A tank_attachment
is attached to a tank and provides additional functionality for the tank or associated taps. Attachments may be created to perform actions when certain events occur on the tank, track statistics, restrict what kinds of actions can be taken on the tank or who can take them, authorize updates to tank components, etc. Attachments can receive asset, but cannot store it, thus attachments which receive asset must specify a sink to deposit asset in once it is received. Tank attachments cannot directly move asset into or out of a tank; only taps can take asset out of a tank, and only sinks can put asset into a tank. Tank attachments may be stateful, and their state will be stored by the hosting tank_object
. Initially, the following tank attachments are specified:
asset_flow_meter
[Stateful] An attachment which can receive funds, and tracks the amount of asset that has flowed through it, then releases the asset to a predefined sinkdeposit_source_restrictor
An attachment which restricts the permissible sources and paths for deposits to the tank, and rejects deposits which do not follow an appropriate path. A tank can have at most one attacheddeposit_source_restrictor
tap_opener
An attachment which, when it receives asset, queues a predetermined tap to be opened automatically after the current deposit stops movingattachment_connect_authority
An attachment which allows a specified authority to update the sink a specified attachment releases asset to
A sink
provides a common interface for moving asset within Tanks and Taps structures. At a data level, a sink is small and simple, storing only the ID of the object receiving the asset; however, the sink logic will record the source of the deposit and the path the asset takes as it moves, and this information may be used when processing the deposit to trigger events, log statistics, or detect errors. Initially, the following sink
types are specified:
- An account ID, to which the asset will be deposited as a balance
- A tank ID, to add asset to the tank balance
- A tank attachment ID, to send asset through a tank attachment which can receive funds
Deposit Path Restrictions By default, tanks can receive asset at any time from any source; however, if a tank is equipped with a deposit_source_restrictor
attachment, deposits to that tank are restricted based on where the asset originated and the path it subsequently traveled to reach the tank. The restrictor contains several patterns that deposit paths are matched against, and if the path a deposit takes does not match any of the patterns, the deposit is rejected.
Tank Lifecycle Once created, a tank can remain in existence indefinitely and can be filled and emptied many times. It is preferred, however, that unused tanks be destroyed. There are two mechanisms by which a tank is destroyed: first is by a destructor tap, and second is by the tank_destroy
operation. A destructor tap is a kind of tap
which can destroy the tank after the tank is emptied, if configured to do so by the tap_open
operation. Any tap
can be created as a destructor tap, but the emergency tap must be a destructor tap. This is expected to be the most common method of destroying a tank, as it can be done within normal usage of the tank. Alternatively, a tank can be destroyed by using the tank_destroy
operation; however, this operation requires the emergency tap authority and is therefore unlikely to be frequently used in practice.
To incentivize the destruction of tanks that are no longer being used, a deposit of core asset is required to create a new tank. This deposit is held for the lifetime of the tank, and is released when the tank is destroyed. The deposit is returned to the account which pays the fee for the operation that destroys the tank; it is not sent through a tap
.
Restricted Assets Some assets specify account whitelists or blacklists, to restrict which accounts may transact in that asset. It is desired that these assets be able to utilize the Tanks and Taps infrastructure without opening the possibility of using Tanks and Taps to circumvent the restrictions on asset ownership; therefore, a restricted asset check will be enforced in three instances. First, when asset is released through a sink
to an account balance, a restriction check will be performed on that account, and if the account is unauthorized to handle the asset, the transaction which opened the tap
will be rejected as invalid. Second, the account paying the fee for a transaction that opens a tap
must be authorized to handle the asset which is released by the tap
. Third, when a sink
receives asset from an account, that account must be authorized to handle the asset. With these checks in place, restricted assets can be used in conjunction with the Tanks and Taps architecture without compromising the efficacy of the asset restrictions.
Pseudocode definitions of these types are provided below:
attachment_id_type {
/// ID of the tank hosting the attachment
tank_id_type tank_id;
/// ID of the attachment on the tank
uint16 attachment_id;
}
type sink = static_variant<tank_id_type, account_id_type, attachment_id_type>;
struct unlimited_flow{};
type tap_flow_limit = static_variant<share_type, unlimited_flow>;
asset_flow_meter {
state_type {
/// The amount of asset that has flowed through the meter
share_type metered_amount;
}
/// The type of asset which can flow through this meter
asset_id_type asset_type;
/// The sink which the metered asset is released to
sink destination_sink;
}
deposit_source_restrictor {
/// This type defines a wildcard sink type, which matches against any sink(s)
wildcard_sink {
/// If true, wildcard matches any number of sinks; otherwise, matches exactly one
bool repeatable;
}
/// A deposit path element may be a specific sink, or a wildcard to match any sink
type deposit_path_element = static_variant<sink, wildcard_sink>;
type deposit_path_pattern = vector<deposit_path_element>;
/// A list of path patterns that a deposit is checked against; if a deposit's path
/// doesn't match any pattern, it is rejected
vector<deposit_path_pattern> legal_deposit_paths;
}
tap_opener {
/// Index of the tap to open (must be on the same tank as the opener)
uint16 tap_index;
/// The amount to release
tap_flow_limit release_amount;
/// The sink that asset is released to after flowing through the opener
sink destination_sink;
}
attachment_connect_authority {
/// Authority that may reconnect an attachment
authority connect_authority;
/// Attachment which may be reconnected
attachment_id_type attachment_id;
}
type tank_attachment = static_variant<asset_flow_meter, deposit_source_restrictor,
tap_opener, attachment_connect_authority>;
immediate_flow_limit { share_type limit; }
cumulative_flow_limit {
share_type limit;
attachment_id_type meter_id;
}
periodic_flow_limit {
state_type {
/// When the limit was created, and thus, when the first period began
time_point_sec creation_date;
}
/// Duration of periods in seconds
uint32 period_duration_sec;
/// ID of the meter tracking released funds; reset when period rolls over
attachment_id_type meter_id;
/// Maximum cumulative amount to release in a given period
share_type limit;
}
time_lock {
/// If true, the tap is initially locked
bool start_locked;
/// At each of these times, the tap will switch between locked and unlocked --
/// must all be in the future
vector<time_point_sec> lock_unlock_times;
}
minimum_tank_level {
/// Minimum tank balance; tap cannot drain tank below this balance
share_type minimum_level;
}
review_requirement {
state_type {
/// This type describes a request for withdrawal waiting for review or
/// for redemption
request_type {
/// Amount requested for release -- reset if reviewer denies request
optional<tap_flow_limit> request_amount;
/// Optional comment about request, max 150 chars -- reset if reviewer
/// denies request
optional<string> request_comment;
/// Starts false, set to true if request is approved, and back to false after
/// a release of funds
bool approved;
}
/// Number of requests made so far; used to assign request IDs
uint16 request_counter;
/// Map of request ID to request
flat_map<uint16, request_type> pending_requests;
}
/// Authority which approves or denies requests
authority reviewer;
}
documentation_requirement {
/* no fields; if this requirement is present, evaluator requires a nonempty
* documentation argument exist, but does no further verification upon it
*/
}
delay_requirement {
state_type {
/// This type describes a request for withdrawal waiting for its delay to pass
request_type {
/// When the request was made
optional<time_point_sec> delay_period_start;
/// Amount requested
optional<tap_flow_limit> request_amount;
/// Optional comment about request; max 150 chars
optional<string> request_comment;
}
/// Number of requests made so far; used to assign request IDs
uint16 request_counter;
/// Maximum allowed number of outstanding requests; zero means no limit
uint16 request_limit;
/// Map of request ID to request
flat_map<uint16, request_type> pending_requests;
}
/// Authority which can veto request during review period; if veto occurs,
/// reset state values
optional<authority> veto_authority;
/// Period in seconds after unlock request until tap unlocks; when tap opens,
/// all state values are reset
uint32 delay_period_sec;
}
hash_preimage_requirement {
/// A string of the form "HASH_ALGORITHM:HASH_HEX_BYTES"
string hash;
/// Size of the preimage in bytes; a preimage of a different size will be rejected
/// If null, a matching preimage of any size will be accepted
optional<uint16> preimage_size;
}
ticket_requirement {
state_type {
/// The type of the ticket that must be signed to unlock the tap
struct ticket_type {
/// ID of the tank containing the tap this ticket is for
tank_id_type tank_id;
/// Index of the tap this ticket is for
uint16 tap_index;
/// Maximum asset release authorized by this ticket
tap_flow_limit max_withdrawal;
/// Must be equal to tickets_consumed to be valid
uint16 ticket_number;
}
/// Number of tickets that have been used to authorize a release of funds
uint16 tickets_consumed;
}
/// Key that must sign tickets to validate them
public_key_type ticket_signer;
}
exchange_requirement {
/// The maximum release amount will be:
/// meter_reading / tick_amount * release_per_tick - amount_released
state_type {
/// The amount of asset released so far
share_type amount_released;
}
/// The ID of the meter to check
tank_attachment_id_type meter_id;
/// The amount to release per tick of the meter
share_type release_per_tick;
/// Amount of metered asset per tick
share_type tick_amount;
}
type tap_requirement = static_variant<immediate_flow_limit, cumulative_flow_limit,
periodic_flow_limit, time_lock, minimum_tank_level,
review_requirement, documentation_requirement,
delay_requirement, hash_preimage_requirement,
ticket_requirement, exchange_requirement>;
tap {
/// The connected sink, if present
optional<sink> connected_sink;
/// The authority to open the tap; if null, anyone can open the tap if they can
/// satisfy the requirements
optional<authority> open_authority;
/// The authority to connect and disconnect the tap. If unset, tap must be connected
/// on creation, and the connection cannot be later modified -- emergency tap must
/// specify a connect_authority
optional<authority> connect_authority;
/// Requirements for opening this tap and releasing asset
vector<tap_requirement> requirements;
/// If true, this tap can be used to destroy the tank when it empties
bool destructor_tap;
}
tank_object {
/// Amount of asset contained in the tank
asset contained_asset;
/// Taps on this tank. ID 0 must be present, and must not have any tap_requirements
flat_map<uint16, tap> taps;
/// Counter of taps added; used to assign tap IDs
uint16 tap_counter;
/// Attachments on this tank
flat_map<uint16, tank_attachment> attachments;
/// Counter of attachments added; used to assign attachment IDs
uint16 attachment_counter;
/// Amount of the deposit paid to create the tank (deposit is always core asset)
share_type deposit_amount;
}
This proposal additionally seeks to add eight operation types to the protocol:
tank_create
Creates a new tank, specifying taps, tap requirements, and sink connectionstank_update
Updates a tank, adding or removing taps (including requirements and sinks)tank_destroy
Destroys a tank (fails if the tank contains asset)tank_query
Pass arguments to tap requirements or tank attachments to perform actions or comply with requirementstap_open
Release funds through a tap, passing arguments as necessary to satisfy tap requirementstap_connect
Connect a tap to a sink, defining a sink on the tank as necessaryaccount_fund_sink
Move asset from an account to a sink (such as a tank)sink_fund_account
Virtual operation, generated when a sink deposits asset into an account balance
Pseudocode definitions of these operations are provided below:
tank_create {
/// Account that pays fee and deposit
account_id_type fee_payer;
/// Deposit of core asset paid to create the tank
share_type deposit_amount;
/// Type of asset the tank will contain
asset_id_type tank_asset;
/// Taps on the tank -- index 0 must be present and must have no tap_requirements
vector<tap> taps;
/// Attachments on the tank
vector<tank_attachment> attachments;
}
tank_update {
/// Account that pays fee
account_id_type fee_payer;
/// Authority to update tank: must match tank->taps[0].open_authority
authority update_authority;
/// ID of tank to update
tank_id_type tank_to_update;
/// IDs of taps to remove
flat_set<uint16> taps_to_remove;
/// New taps to add
vector<tap> taps_to_add;
/// IDs of attachments to remove
flat_set<uint16> attachments to remove;
/// New attachments to add
vector<tank_attachment> attachments to add;
}
tank_destroy {
/// Account that pays fee
account_id_type fee_payer;
/// Amount of deposit reclaimed for destroying tank
share_type deposit_amount;
/// Authority to destroy tank: must match tank->taps[0].open_authority
authority destroy_authority;
/// ID of tank to destroy; tank must be empty of asset to destroy
tank_id_type tank_to_destroy;
}
tank_query {
/// Account that pays the fee
account_id_type fee_payer;
/// Authorities required to perform the queries
flat_set<authority> required_authorities;
/// ID of the tank to be queried
tank_id_type tank_id;
/// Whether to query a tap or an attachment
enum query_enum { tap_query, attachment_query};
query_enum query_type;
/// ID of the tap or attachment to query
uint16 query_id;
/// Key-value map of arguments
flat_map<string, string> arguments;
}
tap_open {
/// Account that pays the fee
account_id_type fee_payer;
/// Authorities required to satisfy the tap's requirements/open authority
flat_set<authority> required_authorities;
/// ID of the tank with the tap to be opened
tank_id_type tank_id;
/// Index of the tap to open
uint16 tap_index;
/// Amount to release from the tap
tap_flow_limit release_amount;
/// Total number of taps opened by this transaction, i.e. due to tap_openers
uint16 taps_to_open;
/// If emptying tank via a destructor tap, the deposit is returned to fee_payer
/// Specify amount of deposit here to enable tank destruction
optional<share_type> claimed_deposit;
/// Key-value map of arguments
flat_map<string, string> arguments;
}
tap_connect {
/// Account that pays the fee
account_id_type fee_payer;
/// Authority to connect the tap: must match
/// tank_id->taps[tap_index].connect_authority
authority connect_authority;
/// ID of the tank holding the tap to be opened
tank_id_type tank_id;
/// Index of the tap to be connected
uint16 tap_index;
/// Sink to connect the tap to; if null, tap will be disconnected
optional<sink> sink_to_connect;
/// Clear the tap connect authority after connecting the tap; if true,
/// sink_to_connect must be specified
bool clear_connect_authority;
}
account_fund_sink {
/// Account that provides funds, and pays fee; must be authorized to handle asset
account_id_type funding_account;
/// Sink that the account deposits asset into
sink destination_sink;
/// Amount that the account is depositing into the sink
asset amount_to_sink;
}
sink_fund_account {
/// Account receiving funds
account_id_type recipient;
/// Amount the recipient received
asset amount_received;
}
Additionally, three new committee parameters will be defined:
max_deposit_path_length
The maximum number of sinks in a deposit pathmax_taps_to_open
The maximum number of taps a single transaction can opentank_deposit_amount
The amount of BTS required for a tank deposit
Many of the tap requirements and tank attachments support modes of user interaction which may or may not result in the immediate movement of asset. Those interactions which do cause movement of asset are performed using the tap_open
operation, and those which do not are performed using the tank_query
operation. Both of these operations will accept arguments specifying what actions should be taken on which requirements/attachments.
Initially, the following tank attachment queries will be supported:
asset_flow_meter
- Reset the meter to zero (No arguments)
attachment_connect_authority
- Reconnect the specified attachment (Accepts a
sink
argument)
- Reconnect the specified attachment (Accepts a
The following tap requirement queries will be supported:
review_requirement
- Create a request to open the tap (Accepts a
request_type
argument and an optional string reason) - Resolve a request to open the tap (Accepts a request ID, a boolean decision, and an optional string reason)
- Cancel a request (Accepts a request ID and an optional string reason)
- Consume an approved request and open the tap (Accepts a request ID)
- Create a request to open the tap (Accepts a
documentation_requirement
- Document the reason for the release of asset (Accepts a string reason)
delay_requirement
- Create a request to open the tap (Accepts a
request_type
argument) - Veto a request to open the tap (Accepts a request ID and an optional string reason)
- Cancel a request (Accepts a request ID and an optional string reason)
- Consume a matured request and open the tap (Accepts a request ID)
- Create a request to open the tap (Accepts a
hash_preimage_requirement
- Reveal the hash preimage and open the tap (Accepts a data buffer)
ticket_requirement
- Redeem a ticket and open the tap (Accepts a
ticket_type
argument)
- Redeem a ticket and open the tap (Accepts a
exchange_requirement
- Reset the amount released and reset the meter (No arguments)
Please note that while this specification attempts to provide a sufficient level of technical detail to convey the essence of Tanks and Taps, some detail has been elided for brevity, and the final implementation may diverge from the specification in order to improve the correctness, stability, efficiency, maintainability, or functionality of the Tanks and Taps framework.
The proposed modifications to the BitShares protocol will add eight new operation types, one new database object, and three new committee-controlled chain parameters. These modifications add new features, and do not modify or restrict any existing features, and therefore all currently supported use cases should be unaffected. In return, however, the new features will provide a powerful platform for financial dapp and smart contract development, and this platform can be augmented to become even more powerful in the future, requiring only minimal changes to support additional use cases. The proposed changes, in conjunction with future updates will make BitShares increasingly suitable as a financial dapp platform, notably offering dapp developers simplified development and a reduced time to market as compared with Turing Complete Smart Contract Platform alternatives which offer greater flexibility at the price of increased complexity.
This document is created for the betterment of humanity and is hereby placed into the public domain.