Skip to content

Commit

Permalink
Merge pull request #61 from Convex-Dev/develop
Browse files Browse the repository at this point in the history
Merge latest changes
  • Loading branch information
mikera authored Feb 21, 2025
2 parents 5994040 + 7b9042d commit 74c8a47
Show file tree
Hide file tree
Showing 21 changed files with 1,164 additions and 183 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ yarn-error.log*
/.project
/.settings/

.lsp/.cache
1 change: 0 additions & 1 deletion .lsp/.cache/db.transit.json

This file was deleted.

10 changes: 10 additions & 0 deletions docs/cad/000_principles/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ It is therefore necessary to place a bound on the size of resources used. This i

Where input to Convex may be effectively unbounded (e.g. the size of data structures such as Vectors), implementations MUST NOT attempt O(n) or greater operations on such structures unless these operations are protected by resource constraints (e.g. accounting for juice costs, memory allowances).

### Conflict-free replication

We have designed lattice technology so that it can operate as a conflict-free replicated data type: this enables scalable systems without the need for locking and synchronisation between distributed processes. Arguably this is the *only* practical way to achieve decentralised consensus at the scales we envisage.

This places some restrictions on technical implementation:
- Message handling should be idempotent, i.e. repeated receipt of identical messages should have no effect (and consume minimal resources)
- Data structures must be designed for efficient CRDT merge operations
- Systems must be designed to operate on the latest concurrent immutable state, with the knowledge that this state may change as it converges towards eventual consistency
- Situations which are ordering-dependent must use CPoS to reach consensus on ordering (this mostly applies to the ordering of Convex transactions at present, but may apply to other lattice merge operations in future)

## General Design Philosophy

### Security First
Expand Down
128 changes: 109 additions & 19 deletions docs/cad/019_assets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ The Convex Asset Model is a universal system for expressing and controlling digi

A key design goal is therefore that the API is universal and extensible, in the sense that it can be used to handle a diverse ecosystem of digital assets, including asset types that have not yet been invented. By way of example, it should be possible to use the same API to transfer a quantity of a fungible token:

`(asset/transfer [currency.USD 10000] destination-address)`
`(asset/transfer destination-address [currency.USD 10000] )`

As it is to transfer a set of numbered NFTs:

`(asset/transfer [asset.nfts #{101 102 105}] destination-address)`
`(asset/transfer destination-address [asset.nfts #{101 102 105}] )`

So... why is this important?

Expand Down Expand Up @@ -47,13 +47,13 @@ Examples:

### Asset Implementation

Asset logic MUST be implemented by an Actor on Convex. This actor may be referred to as the "asset implementation".
Asset logic MUST be implemented by an actor on Convex. This actor may be referred to as the "asset implementation" or "asset actor".

An asset MAY map one-to-one to an actor, however a single actor MAY implement multiple assets. This allowance is primarily for efficiency reasons: if many assets share the same on-chain logic, it makes sense for a single actor to implement them all rather than deploying new actors for each one.

The use of an actor to provide the asset implementation is important for two reasons:

- It allows for the development of new types of compatible assets: these simply need to provide a new implementation actor and they can be used according to the standard asset model, often without needing to change existing code.
- It allows for the development of new types of compatible assets: these simply need to provide a new implementation and they can be used according to the standard CAD19 asset model, often without needing to change existing code.
- Actors allow for trusted code execution and governance, providing assurance that digital assets will behave correctly and not present unacceptable security risks

### Asset Path
Expand All @@ -62,14 +62,14 @@ An asset path is a descriptor that identifies an asset. Asset paths are importan

An asset path MUST be either:

- The Address of the actor that provides the asset implementation - e.g. - `#1234` is a valid asset path referring to the asset implemented by the actor at the Address `#1234`
- A Vector where the first element is the Address of the actor, and the remainder of the Vector is interpreted by that Actor on an implementation defined basis. e.g. `#[2345 :foo :bar]`
- The address of the actor that provides the asset implementation - e.g. - `#1234` is a valid asset path referring to the asset implemented by the actor at the address `#1234`
- A vector where the first element is the address of the actor, and the second element of the vector is a scoped value interpreted by that actor on an implementation defined basis. e.g. `[#2345 :foo]`. The asset actor will see the scoped value as `*scope*` when called.

A Vector-based asset path SHOULD be used to allow a single actor to implement many different digital assets, e.g.

- Currencies might be designated by an asset path of `[#123456 :USD]`
- Derivative contracts such as put options might have an asset path that includes the underlying asset, strike price and expiry time e.g. `[#98765 [#12345 :USD] 12500 1741948885345]`
- Bets on a football match might specify the match date and selected winner `[#8978 "2023-6-06" "Manchester United"]` (note that a bet on a different outcome of the same match would be a different fungible asset since they are not mutually fungible)
- Currencies might be designated by an asset path like `[#123456 :USD]`
- Derivative contracts such as put options might have an asset path that includes the underlying asset, strike price and expiry time e.g. `[#98765 [[#12345 :USD] 12500 1741948885345]]`
- Bets on a football match might specify the match date and selected winner `[#8978 ["2023-6-06" "Manchester United"]]` (note that a bet on a different outcome of the same match would be a different fungible asset since they are not mutually fungible)

### Quantities

Expand All @@ -79,18 +79,22 @@ Because we want to enable innovation in the types and representations of assets,

Quantities for any asset MUST be a **commutative monoid** (in the mathematical sense). This requirement is necessary in order for addition, subtraction and comparison of asset quantities to behave in well-defined ways.

Assets MUST define an **addition** function enable quantities to be additively combined, i.e. given any two quantities of an asset it should be possible to use the addition function to compute the total quantity. This is equivalent to the addition function of the commutative monoid.
Assets MUST define an **addition** function enable quantities to be additively combined, i.e. given any two valid quantities of an asset it MUST be possible to use the addition function to compute the total quantity. This is equivalent to the addition function of the commutative monoid.

Assets MUST define a **comparison** function enabling quantities to compared, i.e. given any two quantities of an asset it should be possible to use the comparison function to determine if one quantity is a subset of the other. This is equivalent to the algebraic pre-ordering of the monoid.
Assets MUST define a **comparison** function enabling quantities to compared, i.e. given any two quantities of an asset it MUST be possible to use the comparison function to determine if one quantity is a subset of the other. This is equivalent to the algebraic pre-ordering of the monoid.

Assets MUST define a **subtraction** function enabling quantities to be subtracted, i.e. given two quantities of an asset where the first is "larger" than the second (as defined by the comparison function), it should be possible to subtract the second value from the first and get a result that is also a valid quantity.
Assets MUST define a **subtraction** function enabling quantities to be subtracted, i.e. given two quantities of an asset where the first is "larger" than the second (as defined by the comparison function), it MUST be possible to subtract the second value from the first and get a result that is also a valid quantity.

Assets MUST define a **zero** quantity that logically represents an empty holding of an asset. This zero value is the identity element of the commutative monoid. The zero quantity itself will usually an "empty" value such as `#{}` or `0`.
Assets MUST define a **zero** quantity that logically represents an empty holding of an asset. This zero value is the identity element of the commutative monoid. The zero quantity will usually be an "empty" value such as `#{}` or `0`.

The zero quantity SHOULD be the default balance of all accounts. Logically, an account should have a zero holding of an asset until some quantity of the asset is otherwise obtained. An example of an exception to this might be an asset implementation that gives a free non-zero quantity of the asset to all accounts, though this is probably unwise given the obvious potential for abuse.

For any given Asset, it MUST be possible to identify a Holding of the Asset for a given Account, where the Holding is the Quantity of the Asset that the Account owns.

An asset implementation MAY permit quantities to be specified using non-canonical values, provided that it MUST behave as if the equivalent canonical quantity was provided. For example, a NFT actor could reasonably consider a non-set value such as `123` to refer to the singleton set `#{123}` representing a single numbered NFT.

An asset implementation MAY attempt to produce reasonable results where quantity operations are passed arguments that are not normally valid, e.g. subtracting `3000` from `1000` would reasonably produce the result `-2000`, even though this is not a valid balance. However users of assets SHOULD NOT rely on this behaviour (in this case, subtraction is invalid because the first argument is "less than" the amount subtracted).

When passed as an argument to an asset implementation, the value `nil` MUST be treated as the zero quantity. This requirement ensures that a zero-equivalent value is known for all implementations, and can be used by generic code without having incurring the cost of explicitly querying the zero value.

#### Quantity examples
Expand Down Expand Up @@ -132,15 +136,23 @@ Offers SHOULD remain open at the discretion of the offering account. However, cl

## User API

The user API for the Asset Model is provided by the library `convex.asset`.
The user API for the Asset Model is provided by the library `convex.asset`. Examples below assume the user has imported an alias for `convex.asset` library e.g.

```clojure
(import convex.asset :as asset)
```

Users do not need to use the `convex.asset` library to work with Convex digital assets - they are free to access the underlying actor functions directly. However the user API presents a convenient, well-tested interface that should be suitable for most purposes.

:::warning
`convex.asset` is a library, which means that functions run in the context of the account that invokes them. Only do this with a trusted library, i.e. be sure you have imported the correct library. The address of the standard `convex.asset` is `#65` on Protonet.
:::

### `balance`

The `balance` function gets the total quantity of an asset currently held by an account.

```
```clojure
(asset/balance some-fungible-asset *address*)
=> 1500

Expand All @@ -153,25 +165,103 @@ The returned value should always be a valid quantity for the specified asset. In

If the address argument is omitted, the balance for the current account is queried (i.e. an implicit `*address*` argument is used to specify the current account).

### `transfer`

The `transfer` function transfers a quantity of an asset to a recipient.

```clojure
;; This transfers to account #13 1000 units of MY_TOKEN
(asset/transfer #13 [MY-TOKEN 1000])
```

As an alternative form, `transfer` may be called with an asset path and quantity as separate arguments:

```clojure
;; This transfers to account #13 1000 units of MY_TOKEN
(asset/transfer #13 MY-TOKEN 1000)
```

The receiving account MAY implement a `^:callable receive-asset` function, in which case instead of directly transferring the asset, the asset will be *offered* and `receive-asset` will be called. Actors could, for example, use this so that they can automatically reject assets that they are not supposed to receive, and `accept` assets that they can handle.

`transfer` MUST fail if the caller does not have the quantity of assets specified. This SHOULD be a `:FUNDS` error, since this is conventionally used to indicate the lack of a sufficient asset quantity.

### `offer`

Making an `offer` makes a quantity of an asset available for a subsequent `accept` by a nominated receiver.

```clojure
(asset/offer receiver [fungible-token-address 1000])
```

:::warning
If executed, an offer stays open until explicitly closed (by setting the quantity to zero). Users are advised to be cautious about making offers unless they expect the offer to be immediately accepted on terms they approve of (or rolled back in case of error) since the receiver may accept the offer at some arbitrary point in the future.
:::

Asset implementations SHOULD reclaim memory by removing any offer records if the offer is set to the zero quantity. Failure to do so may result in wasted memory allowances.

### `get-offer`

The current offer of an asset from any sender to any receiver can be obtained with the `get-offer` function:

```clojure
(asset/get-offer fungible-token-address sender receiver)
```

Asset implementations MUST return the current offer, i.e. the quantity last specified via `offer` from the sender to the receiver minus any quantities subsequently accepted with `accept`.

Asset implementations MUST return the zero value if an offer does not exist.

### `accept`

An offer may be accepted by a receiver as follows:

```clojure
(asset/accept sender [fungible-token-address 1000])
```

Often, the sender will be the `*caller*` to an actor function which needs to take a quantity of an asset from the caller, e.g. as part of an atomic value exchange or as a payment of fees.

Asset acceptance MUST fail with a `:FUNDS` error if the quantity could not be accepted because it exceeds the sender's current balance.

If accepted, the asset implementation MUST:
- Transfer the specified quantity of the asset from the sender to the receiver
- Subtract the accepted quantity from the offer (which may result in it becoming zero)

### `owns?`

Tests whether a given address owns the specified quantity of an asset:

```clojure
(asset/owns? #1456 fungible-token-address 1000)

;; alternative argument layout with an asset vector
(asset/owns? #1456 [fungible-token-address 1000])
```

The `owns?` function MUST return true if and only if the balance of the owner is at least as large as the specified quantity, i.e. the owner would normally be able to `transfer` this amount.

### `total-supply`

The `total-supply` function obtains the current total supply of an asset.
The `total-supply` function obtains the current total supply of an asset, if available.

```clojure
(asset/total-supply my-token)
=> 1000000000000000000
```

The `total-supply` function MAY return `nil` if the asset does not support efficient computation of the total supply.
The result (if available) MUST equal the sum of all current balances of the asset.

The `total-supply` function MAY return `nil` if the asset does not support computation of the total supply.

The total supply of an asset may change during the lifetime of an asset, e.g. because new quantities are minted or burned.
The total supply of an asset MAY change during the lifetime of an asset, e.g. because new quantities are minted or burned.

Asset implementations SHOULD cache the total supply by keeping track of the total quantity of an asset created or destroyed, e.g. via mint and burn operations. This allows the total supply to be provided without adding up all current balances, which is likely to be unreasonably expensive for callers.

## Security considerations

### Untrusted assets

Assets are implemented by Actors in the Convex asset model, and as such there are a number of issues that may arise if untrusted assets are used.
Assets are implemented by actors in the Convex asset model, and as such there are a number of issues that may arise if untrusted assets are used.

The general recommendation is that users SHOULD NOT interact with untrusted assets.

Expand Down
Loading

0 comments on commit 74c8a47

Please sign in to comment.