Smart Contract interactions on Partisia Platform
An interaction is an action of a contract that invokes an action in another contract. Smart contract interactions on Partisia Platform conform to a message passing paradigm with the following properties:
- Event-driven: Users and other contracts interact with a contract through designated entry points.
- Asynchroynous execution: It allows for the execution of events without a defined order or dependency between them.
- Atomic events: Events within the smart contract execute atomically. In the event of a failure during event execution, the smart contract's state is reverted to its condition prior to the event's initiation.
- Non-atomic event trees: Child events, spawned by parent events, do not inherit the parent event's atomic context.
These properties combine to a system that is incredibly scalable, but require more care in designing smart contract interactions than other blockchain architectures like Ethereum Virtual Machine (EVM).
Comparison with other systems
While smart contract blockchains are commonly compared with the Microservices architectural pattern, Partisia Platform is even more so comparable.
Feature | Partisia Platfrom | EVM | Microservices | Monolithic application |
---|---|---|---|---|
Decentralized | Yes | Yes | Yes | No |
Message Parsing | Yes | Yes | Yes | No |
Local State Rollback | Yes | Yes | ? | Depends |
Public Ledger and public state | Yes | Yes | No | No |
Scalable | Yes | No (Requires layer 2) | Yes | No |
Message Call Stack | Yes | Yes | No | Yes |
Global State Rollback | No | Yes | No | Depends |
Partisia Platform differs from EVM-based systems in several key aspects. Notably, it does not support Global State Rollback. In EVM, interaction failures typically lead to the rollback of the entire interaction call tree. This behavior is not supported on Partisia Platform. Additionally, permissions cannot be checked retroactively on Partisia Platform, unlike in EVM environments.
Atomic events
An atomic action is a series of operations that either completes successfully in its entirety or fails completely, leaving no intermediate state. Partisia Platform guarantees that each event/action is atomic. Consequently an atomic action is either:
- Successful: in which case the changes are committed to the smart contract state and visible for everyone.
- Failed: in which case any changes are rolled back to before the action was attempted.
Atomicity on the Partisia Platform works in the same way as in a database system.
User-to-contract interaction model
Users interact with Partisia Blockchain by a signed transaction to a smart contract. The signature ensures that the user has authorized the transaction. Clients must cryptographically sign transactions they send to ensure authenticity and non-repudiation.
When Partisia Platform receives a signed transaction and verifies the signature it spawns an event (dotted line), which is forwarded to the smart contract. The event carries information about which action for the smart contract to perform.
Upon receiving the event, the smart contract executes the specified action. This execution involves executing the smart contract's code, resulting in an update to the smart contract's state. The action code execution occurs atomically as explained above, meaning that the action can be:
- Successful: In which case the state changes are committed to Partisia Platform and visible for everyone.
- Failed: In which case no state will change on Partisia Platform.
The following model illustrates a case where the user votes on a resolution using a voting contract:
Contract-to-contract interaction model
Smart contracts can interact with other smart contracts, much like users can. Because contracts live on Partisia Platform they can interact with another smart contract without sending a signed transaction. Instead, they create an event directly, which is forwarded to the receiving contract.
Only successful actions can spawn events. Any action can spawn any number of events to other contracts, as will be discussed below.
To illustrate, consider a scenario where someone holds an NFT auction, held in USDT (or some other token). The user wants to bid on this auction.
The scenario proceeds as follows: the user sends a bid
action on the auction contract.
To perform the bid
action, the auction contract initiates an event that triggers a
transfer
action on the USDT contract, which will transfer the specified USDT amount
from the user's account to the auction contract. The USDT is held in the auction
contract as escrow payment.
Any of these two actions (bid
and transfer
) can fail:
- If the
bid
action fails, the auction contract will forget about the bid, and will never spawn thetransfer
event. - If the
transfer
action fails it will not transfer the USDT tokens. Below we will discuss ways to handle this failure case.
If the transfer
action succeeds, the USDT contract will transfer the USDT tokens,
and the auction contract will register the original bid.
Contract-to-contract with callback
Smart contracts can additionally wait for answers to their previously sent events, by declaring a callback, which is an anticipatory event for a subsequent action within the same contract.
Upon completion, a spawned event checks for a declared callback. If a callback is defined, the event sends a response event back to the originating smart contract. This mechanism enables the initial smart contract to make decisions based on the outcomes of delegated actions.
Expanding on the auction example: The auction contract must know whether the
escrow transfer
was successful or not, such that it can update the highest bid.
The scenario proceeds as follows: The user sends a bid
action on the auction contract.
To perform the bid
, the auction contract initiates a transfer
action on the USDT contract to transfer
the specified USDT amount from the user's account to the auction contract as an escrow payment.
The auction contract registers a callback to receive the result of the transfer
action.
The USDT contract executes the transfer
action, which may succeed or fail.
Regardless of the transfer action's outcome, the auction contract's callback is invoked,
allowing it to assess the transfer result and determine whether to accept the bid.
Possible results:
- If the
bid
action fails, the auction contract will forget about the bid, and will never spawn thetransfer
event. - If the
transfer
action fails, the USDT contract will not transfer the USDT tokens. A failure callback will be sent to the auction contract, which then removes the bid. - If the
transfer
action succeed, the USDT contract will transfer the USDT tokens. A success callback will be sent to the auction contract, which then updates the best bid.
Contract-to-contract in a chain
Smart contracts can continue to interact with contracts from their callbacks, allowing long chains of interactions. This can be used to check permissions and payments before performing a privileged action, like sending an NFT.
To illustrate, consider a case where someone wants to buy some COOL tokens using a shop contract made to buy COOL tokens in exchange of USDT tokens.
The scenario proceeds as follows:
- The customer sends a
buy
action on the shop contract. - The shop contract requests the payment from the USDT contract using the
transfer
action, with a callback. - The USDT contract attempts to transfer the USDT, but this may fail if the customer does not have enough USDT in their account.
- The shop contract callback is triggered, and the shop contract checks whether the transfer succeeded.
- The shop contract asks the COOL contract to transfer COOL to the customer, without a callback.
- The COOL contract transfers COOL to the customer.
Possible results:
- If everything succeeds, the customer will end up being some COOL tokens richer, and some USDT poorer.
- If the USDT
transfer
fails, the shop contract will refuse to send COOL tokens. No COOL or USDT will have traded hands.
Contract-to-two-contracts with one callback.
Warning
*This functionality is a footgun. Please do not use it.
Smart contracts can make several interactions at the same time, and can wait for all of the interactions to finish before a callback. This is advanced functionality with limited use cases. Using this functionality securely requires a deep understanding of the properties mentioned at the top of the article.
Pitfalls:
- Race conditions: Interactions made at the same time do not run in order.
- Non-atomic event tree: As explained above, individual events are atomic, but the event tree as a whole is not atomic.
Use cases where this approach may be beneficial include:
- Permission checking across multiple contracts.
- Payment transactions involving multiple tokens.
- Other interactions that do not modify the system state.
Use cases where this can be inconsistent and most likely insecure:
- Permission checking at the same time as side-effects.
- Parallel incoming and outgoing transfers.
- Side effects
- Permission granting and permission checking at the same time.
Extending the shop scenario: The shop owner wants to give a 10% discount to the shoppers using the ICECOLD token.
The scenario proceeds as follows:
- The customer sends a
buy
action on the shop contract. - The shop contract requests the payment from the USDT contract using the
transfer
action and asks the ICECOLD contract whether the user has any ICECOLD, along with a callback. - The USDT contract attempts to transfer the USDT, but this may fail if the customer does not have enough USDT in their account.
- The ICECOLD contract checks user balance. This will fail if the user does not have any ICECOLD.
- The shop callback is triggered, and the shop checks that the
transfer
succeeded and whether thebalance check
succeeded. - The shop asks the COOL contract to transfer COOL to the customer, without a callback.
- The COOL contract transfers COOL to the customer.
Possible results:
- If everything succeeds, the customer will end up being some COOL tokens richer, and some USDT poorer.
- If everything succeeds except the ICECOLD check, the customer will also end up being some COOL tokens richer, and some USDT poorer.
- If the USDT
transfer
fails, the shop will refuse to send COOL tokens. No COOL or USDT will have traded hands.
In the case of payment systems you need to check that the payment has been sent, and is in your possession before sending the bought asset. Such functionality should not be implemented by sending events that transfer payment assets and bought assets in the same event group. Doing so may result in serious exploits.