Well, guess what I was doing on the plane?
Actor: a smart contract or private key, referenced by address
Issuer: an actor (typically a smart contract) that has the ability to define and issue state according to an immutable specification (non-contract actors can only issue a “basic” ether-like token)
Asset storage tree: merkle tree of key-value pairs, replacing the eth balance tree. The key corresponds to the issuing actor (in the case of ether, this is the 0x0 address). The value corresponds to an asset whose data type is defined by the issuing actor.
The asset’s type can be one of 3 options, with the following formats:
- A uint256 (ether, ERC-20)
- A list of bytes objects (ERC721, NFTs)
- A mapping of bytes32 → uint256 (ERC-1400, PFTs)
The asset balances are publicly available, and can be queried using an opcode to return the data from the tree at the specific issuer address.
The issuer should provide an entry in the ABI for non-essential information about the asset (such as ticker symbol, etc). The issuer contract can define this for convenience as well.
The issuing actor defines the asset’s structure by calling an opcode that allows the issuing actor to define the asset’s type and initialize the supply characteristics. Nodes can use this to store tracking information. This information is as follows for each of these formats:
- Total supply of 0 (represented by uint256)
- Empty merkle tree root hash
- Total supply of 0 (represented by uint256)
The issuer can only issue one asset.
Transferring an asset requires the issuer to send a message with the following data field:
SEND_OPCODE, issuer’s address, recipient’s address, assets to transfer (also matches asset structure). In order to be successful, this message’s asset structure must meet the conditions corresponding to it’s asset type:
- Asset is a uint256 GT 0, but LEQ the balance in asset storage
- Asset is a non-empty subset of entries in owner’s asset storage list
- Asset is a list of key-value pairs, where the value is a uint256 GT 0, but LEQ the balance of the sub-asset referenced by the key.
If these conditions are not met, the transfer reverts execution.
Assets can have complex requirements for a transfer function if the issuer defines a transfer predicate. This predicate must match the signature transfer(to: address, from: address, asset: asset_type)
and is called against the issuer’s address. The function can revert the transfer for whatever reason, as well as make internal state changes (if necessary) to it’s own storage (never the owner or receipient). If no matching function is present, the asset is assumed freely moveable (this takes care of selfdestructed contracts as well as ether) as the predicate cannot revert.
The account receiving the asset will then merge them according to the following rules:
- Current balance adds received balance. Reverts if GEQ 2**256. Transferred balance is deducted from sender’s account.
- Received entries are appended to the receiver’s list, and removed from the sender’s. Empty lists can be deleted.
- Balance of sub-assets are merged according to rule 1. Empty keys can be deleted.
If the structure does not exist in the receiver’s account for the issuer’s key, a new entry will be created.
The receiver (if a contract) can define a receive function that matches the signature recieve(issuer: address, from: address, asset: asset_type)
. It can revert or do whatever it would like with this information as it sees fit. If no function is defined, the transfer is successful
Transaction receipts should contain a summary of all transfer messages that occurred in a given transaction.
An asset can be destroyed by sending a transfer message to the 0x0 account. This triggers the explicit removal of the asset from data storage as the 0x0 account does not store any tokens except ether (as it did issue all ether). Nodes will remove this entry from state.
An asset can only be minted by the issuer with the same opcode used to encode the structure in the orignal issuance, and can be passed by transfer message to another account, if desired. The rules for minting are as follows:
- Number minted must not overflow total supply
- ID minted cannot be in list of all assets (merkle tree proof)
- Sum of the increases of all sub-assets cannot overflow the total supply.
Updates are captured if the minting does not violate these invariants.
Positive attributes:
- Stateful message passing of assets (Ether receive logic! Token receive logic!)
- Decentralization of asset storage (security and storage rent benefits!)
- Decentralization of message passing (sharding benefits!)
- Reduced complexity of token issuance contracts
- Assets as a first class citizen (not just eth!)
- May enable token gas payments?
Negative attributes:
- Increased complexity at protocol layer
- More inflexible API (did I miss one?)
- Requires changes to ether balances
- 4 opcodes added (register, issue, transfer, query)
- NFT MT is probably a DoS vector. There is probably a better way to prove nonmembership in the set of already minted UIDs that the issuer may not own.
Other ideas:
- Add a bool asset type, used primarily for ownership and access rights. Could also be an enumeratation.