Asset Permission System
The Asset Permission System (APS) is one of Ralph's unique features. It explicitly stipulates the flow of assets at code level, giving confidence to developers and users of the smart contracts that all asset transfers happen as intended. Together with the UTXOs model, it also offers a simpler and more secure user experience by eliminating the token approval risks in systems such as EVM.
Alephium uses the sUTXO model where assets, including the native ALPH and other tokens are managed by UTXOs while smart contracts and their states are managed using account-based model.
This has a few implications:
- Simple asset transfers among users only require UTXOs, which is battle tested for its security in managing assets. Here no smart contracts are involved.
- When smart contracts need to transfer assets on behalf of the owners, no separate approval transactions are required. The approval is implicit in the UTXO model: if the input that contains a particular token is authorized to be spent in the transaction, then the owner has already given consent to the usage of that token in the context of this transaction, meaning that the smart contracts that get invoked in the same transaction can potentially transfer the token.
Now the question is: in the second situation, how can we make sure that the assets implicitly approved in the transaction using the UTXO model can be handled securely by the smart contracts? The answer is Ralph's Asset Permission System (APS).
Flow of Assets
To interact with the smart contracts in Alephium, a transaction needs
to execute a TxScript
. In the following example transaction, there
are two inputs, one fixed outputs and a TxScript
:
----------------
| |
| |
1 Token A | | 1 ALPH (fixed output)
================> | | ========================>
6.1 ALPHs | <TxScript> | ??? (generated output)
================> | | ========================>
| |
| |
| |
----------------
Two things are worth noting here:
- Even though there is only one fixed output, there will be more
outputs generated for this transaction. The generated outputs
depend on the result of the
TxScript
execution. - The total assets available for
TxScript
(including the smart contracts it invokes) are5.1
ALPHs and1
Token A, because we need to subtract the1
ALPH in fixed output.
Let's say the TxScript
looks something like this:
TxScript ListNFT(
tokenAId: ByteVec,
price: U256,
marketPlace: NFTMarketPlace
) {
let listingFee = marketPlace.getListingFee()
let minimalAlphInContract = 1 alph
let approvedAlphAmount = listingFee + minimalAlphInContract
marketPlace.listNFT{callerAddress!() -> ALPH: approvedAlphAmount, tokenAId: 1}(tokenAId, price)
}
As you may have guessed, Token A is an NFT token and the purpose of
the above TxScript
is to list it through a marketplace smart contract.
The following line of code is of particular interest:
marketPlace.listNFT{callerAddress!() -> ALPH: approvedAlphAmount, tokenAId: 1}(tokenAId, price)
The code inside of the curly braces explicitly approves that
approvedAlphAmount
ALPH and 1
token A are allowed to be spent in
the marketPlace.listNFT
function, even though the total amount of
assets available for TxScript
are 5.1
and 1
for ALPH and token A
respectively.
The following scenarios could happen:
- If
approvedAlphAmount
turns out to be more than5.1
ALPH, then the transaction fails withNotEnoughBalance
error. - If
approvedAlphAmount
is less than5.1
ALPH, say1.1
ALPH, then maximum amount of assets thatmarketPlace.listNFT
can handle are1.1
ALPHs and1
token A.marketPlace.listNFT
does not have access to the rest of the4
ALPHs. - If
marketPlace.listNFT
has not spent the entirety of the approved assets, the remaining assets will be returned back to their owner whenmarketPlace.listNFT
returns.
Let's look a bit closer to the marketPlace.listNFT
function:
Contract NFTMarketPlace(
nftListingTemplateId: ByteVec
) {
// Other code are omitted for brevity
pub fn getListingFee() -> U256 {
return 0.1 ALPH
}
@using(preapprovedAssets = true, assetsInContract = true, updateFields = false)
pub fn listNFT(
tokenId: ByteVec,
price: U256
) -> (Address) {
assert!(price > 0, ErrorCodes.NFTPriceIsZero)
// Only owner can list the NFT
let tokenOwner = callerAddress!()
let (encodeImmutableFields, encodeMutableFields) = NFTListing.encodeFields!(tokenId, tokenOwner, selfAddress!(), commissionRate, price)
// Create the listing contract
let nftListingContractId = copyCreateSubContract!{tokenOwner -> ALPH: 1 alph, tokenId: 1}(
tokenId, nftListingTemplateId, encodeImmutableFields, encodeMutableFields
)
// Charge the listing fee
transferTokenToSelf!(tokenOwner, ALPH, listingFee)
return contractIdToAddress!(nftListingContractId)
}
}
First thing to notice is the annotation for the listNFT
method:
@using(preapprovedAssets = true, assetsInContract = true, updateFields = false)
preapprovedAssets = true
tells VM that the listNFT
method intends
to use some assets and the caller is supposed to approve a set of
necessary assets or else a compilation error will be
reported. Compilation will also fail if the caller tries to approve
assets for a method where preapprovedAssets = false
.
assetsInContract = true
indicates to the VM that the listNFT
method wants to update the asset of the NFTMarketPlace
contract. Compiler will make sure that the listNFT
method indeed
does that or else an compilation error will be reported. In this case,
listNFT
updates the asset of the NFTMarketPlace
contract by
transferring the listingFee
to it:
// Charge the listing fee
transferTokenToSelf!(tokenOwner, ALPH, listingFee)
updateFields
annotation is out of scope for this documentation.
marketPlace.listNFT
method is invoked by TxScript
ListNFT
, as
shown below:
marketPlace.listNFT{callerAddress!() -> ALPH: approvedAlphAmount, tokenAId: 1}(tokenAId, price)
When marketPlace.listNFT
is executed by the VM, it is authorized to
spend 1.1
ALPH and 1
token from the caller of the script. If
marketPlace.listNFT
in turn calls other methods, it can approve a
subset of these approved assets to that method as well. For example,
in marketPlace.listNFT
we have the following code to create a NFT
listing:
let nftListingContractId = copyCreateSubContract!{tokenOwner -> ALPH: 1 alph, tokenId: 1}(
tokenId, nftListingTemplateId, encodeImmutableFields, encodeMutableFields
)
As we can see, marketPlace.listNFT
method approves 1
ALPH and 1
Token A to the copyCreateSubContract!
built-in function from its own
pool of approved assets (1.1
ALPH and 1
Token A), before it sends
the listingFee
to the NFTMarketPlace
contract itself. The flow of
assets is illustrated below:
Caller of the TxScript
(6.1 ALPH; 1 Token A)
||
||
|| Subtract assets in
|| Fixed outputs
||
|| Approves Approves
\/ (1.1 ALPH; 1 TokenA) (1 ALPH; 1 TokenA)
(5.1 ALPH; 1 Token A) ========================> listNFT ========================> copyCreateSubContract!
||
||
|| To self
||
\/
(0.1 ALPH)
As we can imagine, if we have a bigger tree of method calls, the approved fund will cascade from the root of the tree all the way to the leaves like water. Asset Permission System makes this flow of the fund throughout the method calls explicit and enforce constraints to each of the methods as to what tokens and how much of them can be spent.
Going back to the transaction, after the execution of the TxScript
the generated outputs should look something like this:
----------------
| |
| | 1 ALPH (fixed output)
1 Token A | | =========================================>
======================> | | 1 ALPH, 1 Token A (NFTListing contract)
6.1 ALPHs | <TxScript> | =========================================>
======================> | | 0.1 ALPH (NFTMarketPlace contract)
| | =========================================>
| | 4 ALPH - gas (change output)
| | =========================================>
| |
----------------
Summary
Asset Permission System (APS) dictates the flow of assets in smart contracts. The explicit approval of the assets for each method invocation ensures that the methods can never spend more than what they are authorized for. Together with the UTXO model, it offers an asset management solution that is simpler, more reliable and more secure.