Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AIP-040: Application standard for non-fungible tokens #40

Open
3 tasks
fulldecent opened this issue Jul 20, 2019 · 7 comments
Open
3 tasks

AIP-040: Application standard for non-fungible tokens #40

fulldecent opened this issue Jul 20, 2019 · 7 comments

Comments

@fulldecent
Copy link
Contributor

fulldecent commented Jul 20, 2019


AIP: 040
Title: Aion Non-Fungible Token Standard
Author(s): William Entriken
Type: ASC
Status: Discussion
Creation Date: 2019-07-05
Contact Information: github.com@phor.net


📖 OPEN ITEMS

  • TODO: Reference implementation blockers to deploy: https://github.com/fulldecent/aion-aip040/milestone/1

  • TODO: Deploy reference implementation, link here

  • TODO: Discuss naming of things: deauthorize/revoke (compare with AIP-041), and consign/offer

  • Add any steps here for AIP process

Summary

A standard interface for non-fungible tokens ("NFTs"), also known as deeds.

Value Proposition

The following standard provides basic functionality for Aion AVM contracts to track and transfer NFTs. It builds on a wealth of experience seen from existing NFT implementations in the real world.

We considered use cases of NFTs being owned and transacted by individuals as well as being entrusted to third party brokers/wallets/auctioneers. NFTs can represent ownership over digital or physical assets. We considered a diverse universe of assets, and we know you will dream up many more:

  • On-chain assets — in-game items, ownership of contracts, authority to use contracts

  • Certificates — official digital registration of an off-chain thing, identity, proof of authenticity, supply chain

  • Lots — a security which can be redeemed to take possession of an underlying real-world asset

  • “Negative value” assets — loans, burdens and other responsibilities

In general, all houses are distinct and no two kittens are alike. NFTs are distinguishable and you must track the ownership of each one separately.

Remember:

Your ownership of assets on a ledger is only as valid as your trust in the custodian who has physical control of the assets.

Motivation

A standard interface allows wallet/broker/auction applications to work with any NFT on Aion. We provide for simple NFT smart contracts as well as contracts that track an arbitrarily large number of NFTs.

This standard is inspired by AIP-041 Aion Token Standard (DRAFT), ERC-721 Non-Fungible Token Standard and ERC-1155 Multi Token Standard. The Aion Token Standard is sufficient for tracking many types of tokens, however non-fungible tokens are a distinct asset class which is not compatible.

This supposes specific use cases of non-fungible tokens, however the design is created in a way to support all of the identified use cases without prescribing how they do it. Be creative, we encourage you to make extensions and use this in many ways.

Non-Goals

We do not intend to prevent competing non-fungible token standards from developing on Aion. We hope this standard is useful so that other authors will choose to be compatible with this if they will write their own standard.

Success Metrics

  1. Three competing producers of tokens and three competing consumers, are all deployed to production and confirmed interoperable through an interoperability plugfest (i.e. 3 × 3).
  2. Reference implementation and documentation is entirely usable (results in successful compile, testing and deployment) for one (1) human test subject that understands blockchain concepts but has no experience with Aion or Java or access to experts.
  3. Three applications implementing ERC-721 or ERC-1155 (for non-fungible use cases) and running on a production network are successfully ported to and deployed on Aion.

Description

This specification comprises an interface which is adhered to by all conforming AIP-040 producers (i.e. token contracts) and which is expected by all AIP-040 consumers (e.g. wallets/brokers/auction applications).

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

A contract which implements AIP-040 MUST expose at least this Aion application binary interface (the qualified class name is not part of the specification) and follow all prescriptions below:

0.0
org.aion.AIP040NonFungibleTokenContract
Clinit: ()
public static String aip040Name()
public static String aip040Symbol()
public static BigInteger aip040TotalSupply()
public static Address aip040TokenOwner(BigInteger)
public static Address aip040TokenConsignee(BigInteger)
public static String aip040TokenUri(BigInteger)
public static BigInteger aip040OwnerBalance(Address)
public static boolean aip040OwnerDoesAuthorize(Address, Address)
public static BigInteger aip040TokenAtIndex(BigInteger)
public static BigInteger aip040TokenForOwnerAtIndex(Address, BigInteger)
public static void aip040TakeOwnership(Address, BigInteger[])
public static void aip040Consign(Address, Address, BigInteger[])
public static void aip040Authorize(Address)
public static void aip040Deauthorize(Address)

Aion contracts are classes which expose functionality using public static methods. A contract which implements AIP-040 MUST implement each method specified in the AIP040 interface below, including all @apiSpec annotations. Reference is made to the org.aion.avm.Address type and this is imported from the AVM jar.

package org.aion;

import avm.Address;
import java.math.BigInteger;

/**
 * Reference implementation for the AIP-040 Non-Fungible Token Standard
 * 
 * It is intended that this interface can be inherited for every implementation
 * or extension of AIP-040.
 * 
 * @author William Entriken
 */
public interface AIP040 {

  /**
   * Gets the descriptive name for this non-fungible token.
   * 
   * @apiSpec It is unspecified what language this text will be and no
   *          attempt is made to standardize a localization feature.
   * @return  the string representing the descriptive name
   */
  public static String aip040Name() { return null; }

  /**
   * Gets the brief name (e.g. ticker symbol) for this non-fungible token.
   * 
   * @apiSpec It is unspecified what language this text will be and no
   *          attempt is made to standardize a localization feature.
   * @return  the string representing the brief name
   */
  static String aip040Symbol() { return null; }

  /**
   * Returns the total quantity of tokens existing.
   * 
   * @return the count of all tokens existing
   */
  static BigInteger aip040TotalSupply() { return null; }
  
  /**
   * Returns the owner (if any) of a specific token.
   * 
   * @apiSpec All tokens that exist (i.e. they contribute to
   *          <code>aip040TotalSupply</code>) must have a non-null owner.
   * @param   tokenId the token we are interrogating
   * @return          the owner of the specified token, or null if token does
   *                  not exist
   */
  static Address aip040TokenOwner(BigInteger tokenId) { return null; }

  /**
   * Returns the account (if any) to which a certain token is consigned.
   * 
   * @apiNote         A consignee shall have permission to transfer the token.
   * @param   tokenId the token we are interrogating
   * @return          the consignee of the specified token, or null if none is
   *                  assigned or token does not exist
   */
  static Address aip040TokenConsignee(BigInteger tokenId) { return null; }
  
  /**
   * Returns the URI for a specified token.
   * 
   * @apiSpec         Every token in a contract must have a unique URI.
   * @param   tokenId a specific token to interrogate
   * @return          the URI for the specified token
   * @see             RFC 3986
   */
  static String aip040TokenUri(BigInteger tokenId) { return null; }
  
  /**
   * Returns the count of tokens owned by a specified account.
   * 
   * @param  owner a specific account to interrogate
   * @return       the count of tokens owned by the specified account
   */
  static BigInteger aip040OwnerBalance(Address owner) { return null; }

  /**
   * Find whether an owner has authorized access to an authorizee.
   * 
   * @param  owner      the owner account to interrogate
   * @param  authorizee the authorizee account to interrogate
   * @return            true if authorization is active, false otherwise
   */
  static Boolean aip040OwnerDoesAuthorize(Address owner, Address authorizee) { return null; }

  /**
   * Returns the n-th token identifier.
   * 
   * @apiSpec       Calling this method for every value, 0 <= index <
   *                <code>aip040TotalSupply()</code> on a finalized block will
   *                enumerate every token.
   * @apiNote       The order and stability of ordering of tokens is not
   *                specified.
   * @param   index the specified ordinal of token, 0 <= index <
   *                <code>aip040TotalSupply()</code>
   * @return        the token identifier for the n-th token in a list of all
   *                tokens
   */
  static BigInteger aip040TokenAtIndex(BigInteger index) { return null; }

  /**
   * Returns the n-th token identifier for a given account.
   * 
   * @apiSpec       Calling this method for every value, 0 <= index <
   *                <code>aip040TotalSupply()</code> on a finalized block will
   *                enumerate every token for the specified account.
   * @param   owner the account to interrogate
   * @param   index the specified ordinal of token, 0 <= index <
   *                <code>aip040TotalSupply()</code>
   * @return        the token identifier for the n-th token in a list of all
   *                tokens of the specified owner
   */
  static BigInteger aip040TokenForOwnerAtIndex(Address owner, BigInteger index) { return null; }

  /**
   * Transfer specified tokens to the caller, if permitted.
   * 
   * @apiSpec              The implementation must revert if
   *                       <code>currentOwner</code> does not match the
   *                       actual owner of the specified tokens. This prevents
   *                       a race condition due to non-linear settlement of
   *                       transactions on blockchain.
   * @apiSpec              The implementation must revert if the caller is not
   *                       the current owner, is not authorized by the current
   *                       owner and is not the current consignee of the
   *                       token.
   * @apiSpec              The implementation may revert in other
   *                       circumstances and could depend on whether the
   *                       contract is paused or the outcome of the last
   *                       Phillies vs. Blue Jays game.
   * @apiSpec              This method revokes any consignee for each
   *                       specified token.
   * @apiSpec              Either all transfers and specifications are
   *                       followed or the transactions reverts. Partial
   *                       settlement is not allowed.
   * @param   currentOwner the current owner of all the specified tokens
   * @param   tokenIds     specific tokens to transfer
   */
  static void aip040TakeOwnership(Address currentOwner, BigInteger[] tokenIds) {}

  /**
   * Consigns specified tokens to an account, or revokes an existing
   * consignment. Reverts if not permitted. Permission is specified iff the
   * caller is the token owner or the owner does authorize the caller.
   * 
   * @apiSpec         The implementation must revert if
   *                  <code>currentOwner</code> does not match the actual
   *                  owner of the specified tokens. This prevents a race
   *                  condition due to non-linear settlement of transactions
   *                  on blockchain.
   * @param owner     the account that currently owns the specified tokens
   * @param consignee the account to consign to, or null to revoke consignment
   * @param tokenId   the token to consign
   */
  static void aip040Consign(Address owner, Address consignee, BigInteger[] tokenIds) {}

  /**
   * Authorizes an account to consign tokens on behalf of the caller or to
   * take any token owned by the caller.
   * 
   * @param authorizee the account which receives authorization
   */
  static void aip040Authorize(Address authorizee) {}

  /**
   * Deauthorizes an account to consign tokens on behalf of the caller or to
   * take any token owned by the caller.
   *
   * @param authorizee the account which is revoked authorization
   */
  static void aip040Deauthorize(Address priorAuthorizee) {}

}

You cannot use the above code as an implements target for your implementation code. Java does not support class (not instance) methods with dynamic dispatch, so we cannot make an interface for Aion contracts. This is a shortcoming of Java and should be addressed as a Java Specification Request. And/or the author of this document does not know Java well enough.

A contract which implements AIP-040 MUST emit each event as specified in the AIP040Events interface below, including all @apiSpec annotations. Reference is made to the org.aion.avm.Address type and this is imported from the AVM jar.

public class AIP040Events {

    /**
     * Log event for creating a token
     * 
     * @apiSpec          This event must be emitted for each token created.
     * @apiSpec          The tokenId is padded to 32-bytes using signed padding.
     * @param newOwner   the account that is the owner of the token after the
     *                   creation
     * @param tokenId    the identifier for the token which is being created
     */
    protected static void AIP040Minted(Address newOwner, BigInteger tokenId) {
        Blockchain.log("AIP040Minted".getBytes(),
            newOwner.toByteArray(),
            encodeBigInteger32Bytes(tokenId),
            new byte[0]);
    }

    /**
     * Log event for burning a token
     * 
     * @apiSpec          This event must be emitted for each token destroyed.
     * @apiSpec          The tokenId is padded to 32-bytes using signed padding.
     * @param newOwner   the account that is the owner of the token after the
     *                   destruction
     * @param tokenId    the identifier for the token which is being destroyed
     */
    protected static void AIP040Burned(Address newOwner, BigInteger tokenId) {
        Blockchain.log("AIP040Burned".getBytes(),
            newOwner.toByteArray(),
            encodeBigInteger32Bytes(tokenId),
            new byte[0]);
    }

    /**
     * Log event for transferring a token
     * 
     * @apiSpec          When a token is transferred, the consignee is also
     *                   implicitly unset (i.e. set to null).
     * @apiSpec          This event must be emitted for each token transferred,
     *                   even if the transfer was a result of other than
     *                   <code>aip040TakeOwnership</code>.
     * @apiSpec          The tokenId is padded to 32-bytes using signed padding.
     * @param priorOwner the account that was the owner of the token before
     *                   transfer
     * @param newOwner   the account that is the owner of the token after the
     *                   transfer
     * @param tokenId    the identifier for the token which is being transferred
     */
    protected static void AIP040Transferred(Address priorOwner, Address newOwner, BigInteger tokenId) {
        Blockchain.log("AIP040Transferred".getBytes(),
            priorOwner == null ? null : priorOwner.toByteArray(),
            newOwner == null ? null : newOwner.toByteArray(),
            encodeBigInteger32Bytes(tokenId),
            new byte[0]);
    }

    /**
     * Log event for consigning a token
     * 
     * @apiNote         The owner parameter is duplicative because it could be
     *                  looked up using the token identifier. It is included
     *                  here because log events are indexed by topic and
     *                  searching this log by owner is an expected use case.
     * @apiSpec         This log event is NOT fired during a transfer. All
     *                  transfers result in unsetting the consignee and all
     *                  transfers fire the transfer log event. Therefore, also
     *                  firing the consignee log event at that time would be
     *                  unhelpfully duplicative.
     * @apiSpec         The tokenId is padded to 32-bytes using signed padding.
     * @param owner     the current owner of the token which is being consigned
     * @param consignee the consignee being assigned to the token (or null if
     *                  consignment is being revoked)
     * @param tokenId   the identifier for the token which is being consigned (or
     *                  revoking consignment)
     */
    protected static void AIP040Consigned(Address owner, Address consignee, BigInteger tokenId) {
        Blockchain.log("AIP040Consigned".getBytes(),
            owner.toByteArray(),
            consignee.toByteArray(),
            encodeBigInteger32Bytes(tokenId),
            new byte[0]);
    }

    /**
     * Log event for setting an authorized account of an account
     * 
     * @param account    the account which will delegate authorization to
     *                   another account
     * @param authorizee the account which receives the authorization
     */
    protected static void AIP040Authorized(Address account, Address authorizee) {
        Blockchain.log("AIP040Authorized".getBytes(),
            account.toByteArray(),
            authorizee.toByteArray(),
            new byte[0]);
    }

    /**
     * Log event for removing an authorized account of an account
     * 
     * @param account    the account which will revoke authorization from
     *                   another account
     * @param authorizee the account which loses the authorization
     */
    protected static void AIP040Deauthorized(Address account, Address priorAuthorizee) {
        Blockchain.log("AIP040Deauthorized".getBytes(),
            account.toByteArray(),
            priorAuthorizee.toByteArray(),
            new byte[0]);
    }
}

JSON

If the token URI is JSON, or if it is a URL that points to JSON then that JSON MUST conform to the following format.

{
    "title": "Asset Metadata",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Identifies the asset to which this NFT represents"
        },
        "description": {
            "type": "string",
            "description": "Describes the asset to which this NFT represents"
        },
        "image": {
            "type": "string",
            "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
        }
    }
}

The only specified requirement is that the root object is a JSON object and that the properties "name," "description," and "image," if included, will be a String and will have the meaning shown above.

Caveats

  • AVM issue 403: This AIP requires AVM to support the java.math.BigInteger ABI type, currently this feature is not yet released.

Logic

The design and specification are based on ERC-721 with changes based on a new understanding of best practices and use cases that have evolved since that specification was published, as well as best practices that were available at that time but which failed to be recognized.

Walk-through of the design and specification considerations, choices and approach. Provide practical examples of similar implementations, feedback from the community and perspectives on possible concerns.

Transfer Mechanism — Withdraw Pattern

This standard provides only one transfer mechanism: taking ownership of a specific token where you are permitted to do so. Permission is granted either on a per-token basis (consignment) or a per-account basis (authorization). Permission MAY be limited for any other application-specific reasons, temporary or permanent.

static void aip040TakeOwnership(Address currentOwner, BigInteger[] tokenIds) {}

This mechanism is called the Withdraw Pattern. Details and security considerations which show the benefits of this mechanism as applied to smart contracts are presented in the Solidity documentation, Common Patterns.

This protects against specific mistakes and attacks where other, more featured transfer mechanisms are vulnerable:

  • Accidental recipient — where tokens are transferred to a recipient that is not the intended recipient. Some analysis of tokens that were sent to unintentional recipients was included in ERC-223. This is impossible with the AIP-040 standard transfer mechanism because every transfer is performed by the recipient.
  • Wasteful recipient — where the token recipient extracts value by performing costly calculations at the expense of the sender. This is a well-known feature of some blockchain system including Aion. Implementations may make the mistake of passing an unnecessarily large amount of gas to a token recipient as part of a token transfer callback, and this is documented in "Failure to Set gasLimit Appropriately Enables Abuse". This is impossible with the AIP-040 standard transfer mechanism because every transfer is performed by the recipient.

Transfer Mechanism — No Additional Data

This standard's transfer mechanism does not include the ability to pass opaque data (e.g. "user bytes") along with a token. This is a direct consequence of standardizing on the withdraw pattern. The transfer is performed by the recipient so passing bytes to the recipient serves no purpose.

If you had considered using opaque transfer data for an ERC-721 contract for an application-specific transfer such as listing tokens for sale at a user-supplied price:

tokenContract.safeTransfer(owner, auctionContract, tokenId, "price = 888");

then with AIP-040 please consider to use a custom method that more clearly articulates the intent:

tokenContract.consign(owner, auctionContract, {123});
auctionContract.listForSale({123}, 888);

In the vast majority of cases, we see that token owners are interacting with auction contracts by authorizing access on all of their tokens. Therefore consignment is rarely used for those scenarios.

Similarly, this specification does not allow passing data back to the previous token owner, for the same "wasteful recipient" reason explained above.

Transfer Mechanism — Batch

The transfer and consignment mechanisms accept the token IDs in batch as an array. Failure to include batch transfers in ERC-721 was a common source of complaints and a primary motivation for the creation of ERC-1155, a replacement/extension to ERC-721 as well as numerous other draft standardization proposals.

Only batch transfers are allowed. To transfer one token, use takeOwnership with an array having length one.

In the reference implementation, we created a separate method which allowed transfer without using an array. This saves the cost of having to deserialize an array when only one token is transferred. The cost as in the current proposal for transferring one token is 220037 and with a new hypothetical takeOneToken function it is 219536. The savings is 0.22% over baseline. We do not recognize this as an appreciable savings and have chosen the simplicity of having one transfer function, using arrays.

Alternatives considered: include separate batch and non-batch transfer methods.

Interface Detection

All standardized methods and events are named with the prefix "aip040". This disambiguates the meaning entirely. We identify this as best practice for Aion standardized interfaces because Aion allows you to see the called method names for every transaction. On other blockchains the method/function name is hashed and the original name is not recoverable. Therefore on other blockchains a workaround such as ERC-165 is necessary to find the supported standard interfaces. To detect a contract on Aion which implements simply listen for the events are call one of the methods such as aip040Symbol.

Alternatives considered included: use an application-specific prefix like "token" or "nft" (bad, would overlap with other token standards), don't use a prefix (bad, would definitely overlap).

Naming

The word "take" in aip040TakeOwnership is chosen instead of withdraw because it is short, more direct and because some people may not know the difference between withdraw and withdrawal. This choice is also consistent with the Aion Voice Guidelines.

The words "consign," "authorize," and "deauthorize" are chosen in aip040Consign, aip040Authorize and aip040Authorize respectively. These words are chosen to match the plain meaning of these words and avoid jargon such as "approve" in ERC-20, and "setApprovalForAll" in ERC-721.

Events use names as verbs having past tense and title case, AIP040Transferred, AIP040Consigned, AIP040Authorized, AIP040Deauthorized. This is the modern pattern used in smart contracts such as AIP-041 (DRAFT) and ERC-721. Other standards use present simple tense and nouns (e.g. TransferSingle, Approval) such as ERC-20 and ERC-1155. We choose to prefer style from AIP-041. The word "transferred" is used rather than "taken" because it applies more generally, including minting and burning operations.

The methods aip040TokenAtIndex and aip040TokenForOwnerAtIndex allow to access an element of an array and of an array inside a mapping. We recommend as best practice that method names which refer to accessing the element of an array with a specified index (ordinal) should use "at" and accessing the element of a mapping should use "for". This follows the concept that arrays are predicated on an ordinal input, representing a location in a list ("at") and is consistent with Java documentation for lists. We see "for" as a more general predicate which does not indicate location, making it appropriate for mappings. This is a departure from ERC-721 which uses "of," which we see as more relevant for an object property.

Authorize and Deauthorize

The authorize and deauthorize methods are separated. This is consistent with AIP-041 and is a departure from ERC-721, ERC-1155. We find that including booleans as a method argument is usually not best practice and cite Martin Fowler on this topic.

TODO: Harmonize mechanism and naming with AIP-041 fulldecent/aion-aip040#34.

Enumeration

Four methods allow you to query information from an AIP-040 contract which could have been queried by inspecting the transactions, aip040TotalSupply(), aip040OwnerBalance(Address), aip040TokenAtIndex(BigInteger) and aip040TokenForOwnerAtIndex(Address, BigInteger). Maintaining the accounting for these methods requires additional resources than a simpler hypothetical standard could need.

Certain token implementations such as ERC-1155 and the baseline ERC-721 do not provide enumeration of owned tokens. This approach is less expensive to implement on-chain. However wallet applications will need to scan historical blockchain events, possibly for the entire blockchain to find the tokens of a specific owner. These scanned results could be cached.

The decision was made in AIP-040 to require all implementations to support the full transparency of enumeration. This enables lightweight wallet applications that do not require scanning.

JSON

The AIP-040 token metadata standard is copied from the ERC-721 Metadata JSON Schema. This is a widely permissive standard and is compatible with the following more strict specifications:

  • ERC-1155 Metadata JSON Schema
  • Instagram image size requirements
  • 0xcert Base Asset Schema

Token Identifiers

Tokens identifiers are used to differentiate tokens. In the universe of use cases we have reviewed we also expect to see tokens that are a hash of some specific content, for example a hash of a JSON file. This means that we should expect token identifiers for some applications at least in the range [0, 2^256).

In AIP-040, tokens are identified by using java.math.BigInteger (which is shadowed by the Aion virtual machine). These allow positive, negative and zero as valid identifiers. Aion limits this type to 32 bytes, with values ranging from -2^255 thru 2^255-1, inclusive. Java programmers will recognize the similarity with byte which actually has a range from -128 thru 127, inclusive.

TODO: Document this in AVM at aionnetwork/AVM#408 need to update this section.

Authorization

The mechanism for authorizing accounts is a self-contained system. And an owner does not implicitly authorize itself.

Alternative considered: make owner implicitly authorize itself and prevent owner from deauthorizing self. This additional specification would provide no purpose because transfers are specified being permitted from the owner to themself (subject to application-specific restrictions) regardless of whether they authorize themself.

Events

Events must be fired for each minted, burned and transferred token. The events were designed as a database to lookup historical information about tokens. This is why batch token transfers will emit each token separately.

Token identifiers are padded as signed integers to 32 bytes. This is because Aion will mutate log topics to 32 bytes. Aion behavior makes negative BigIntegers smaller than 32 bytes indistinguishable from similar positive integers which are similar (MOD 2^bits).

TODO: Add citation, see aionnetwork/AVM#413

Risks & Assumptions

  • This standard supports only one transfer mechanism which is a subset of what other popular non-fungible token standards support. We have studied all known existing NFT applications before making this recommendation but an additional use case might exist which is not compatible with the proposed approach. Socializing this across a wide audience of NFT programmers will help mitigate this risk.
  • Our success metric includes attracting three deployed NFT applications from Ethereum networks onto Aion. Deployed application have limited resources to expand their products and we plan to explain to them the value of switching to Aion. Since these teams have competing interests, this represents a risk.
  • The AIP-040 reference implementation does not yet have commercial backing and a sizable bounty. AIP-040 and the reference implementation are designed to support applications at any scale. However it is best practice to offer a cash bounty to security researchers to find vulnerabilities in high value targets. The lack of a bounty may disincentive large projects from deploying on Aion. A modest bounty program can be added in the future and funded to an appropriate amount in time.
  • An abbreviated draft period and limited implementations before finalization risk failure to ensure interoperability. The solution is to finalize after numerous implementations are deployed.

Test Cases

Implementations

Dependencies

  • Identify any other AIP's, modules, libraries, or API's that are dependencies for this AIP to be implemented or achieve its value proposition.

References

Standards and API

Vendor issues

Best practices

Copyright

@jennijuju jennijuju added the ASC label Jul 23, 2019
@jennijuju jennijuju changed the title Application standard for non-fungible tokens AIP #013: Application standard for non-fungible tokens Jul 23, 2019
@jennijuju
Copy link
Contributor

jennijuju commented Jul 23, 2019

@fulldecent Thank you for submitting! The AIP number for this can be #40.

@satran004
Copy link

@fulldecent The method name in this AIP starts with aip#. Example: aip010OwnerOf(byte[])

Is it a good practice to include AIP# in the method name ? With AIP# in class name, it seems to be redundant.

@jennijuju jennijuju changed the title AIP #013: Application standard for non-fungible tokens AIP #040: Application standard for non-fungible tokens Jul 29, 2019
@fulldecent
Copy link
Contributor Author

@satran004 Whether this is a best practice or not is still open for discussion, comments welcome.

Related discussion also at https://github.com/fulldecent/aip-xxx-implementation

The specific problem we are trying to avoid is one AIP defines a standard function name like transfer. Then (or at the some time) somebody else proposes another standard, for example ownership of a contract, also using transfer. We are looking for an interface and code reuse pattern where it somebody could implement the token AIP and the contract ownership AIP at the same time.

Another problem is introspection -- querying a contract to learn if it implements a certain interface, for example, this token standard. If you just call transfer on it then this transfer could have many different meanings. But if you call aipxxxtransfer on it then the intent of the caller is unambiguous.

@satran004
Copy link

Thanks @fulldecent. Now I understand the reasoning behind the naming.

If the idea is to avoid conflict when the class implements two AIP interfaces with same method name, does it make sense to have a contract functionality specific prefix instead of AIP#?
May be nftTransfer()

  • What will happen if there is an upgrade to an existing specification in future with a different AIP#? For example: let's say the next version of NFT specification is AIP 500 with few new methods. If a developer wants to upgrade the existing contract to the new specification, then almost all the existing methods in the contract need to be renamed.

Let me give a similar comparison in JSR standard (Java Specification Requirement).
For example: There are two versions of portlet specification. v 1.0 (JSR 168) and v2.0 (JSR 286)
JSR 286 is backward compatible with JSR186.
Both versions have a method processAction() . But there are some additional methods in v2.0.
So a portlet class written for v1.0 is always compatible with v2.0.

@fulldecent
Copy link
Contributor Author

This is the exact problem we are having in the Ethereum space. The problem is that two people will claim to be the authority on NFT. In EIPs, we have ERC-721 and now ERC-1155. The ERC-1155 could not maintain backwards compatibility with ERC-721 because the latter wanted to reuse function names for different purposes. (Not to mention details about ERC-820/821 DRAFT, ERC-841 DRAFT, ERC-875 DRAFT and other competing NFT standards.)

Having namespacing like "nft" will work if there is one editor that has strong editorial control over the corpus of interfaces. However if there are multiple authors then it is enviable that multiple people will compete to standardize the names.

Using an AIP number avoids this problem because there is a central editor that assigns AIP numbers and avoids conflicts.

@jennijuju jennijuju changed the title AIP #040: Application standard for non-fungible tokens AIP-040: Application standard for non-fungible tokens Aug 12, 2019
@jennijuju
Copy link
Contributor

Changing the status to Discussion

@fulldecent
Copy link
Contributor Author

Added specification for tokenId padding at 86374e0 and added specification with rationale to AIP.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants