Stacks Post Conditions vs Solidity's Revert

Nate B.,•web3 development

Intro

Stacks is Layer 2 blockchain leveraging Bitcoin as its foundational layer. Smart contracts are written in Clarity deployed on the Stacks network.

In contrast, Ethereum is a Layer 1 blockchain onto which smart contracts, written in Solidity, are directly deployed.

We are taking a deeper dive into two features, Stacks Post Conditions and Solidity's Revert function.

This isn't an apples to apples comparison, as each feature is defined and operates very differently in each ecosystem. In the end however, both features exist to prevent unwanted behavior when interacting with a smart contract.

Stacks Post Conditions

A unique component of the Stacks blockchain is the notion of post-conditions.

Post conditions are a security feature built into the blockchain itself that will cause a transaction to abort in the event the defined conditions are not met.

Developers do not need to write post conditions into the Clarity smart contracts. Instead, they are inherent in the use of specific functions that involve the transfer of assets (STX, Fungible and Non-Fungible Tokens), requiring that the post conditions be passed (and met!) when broadcasting a transaction on the Stacks network.

Stacks network nodes can be configured to not require post conditions when broadcasting a transaction, but this removes the security that post conditions provide. Most often, one will see Allow mode associated with DeFi protocols.

When interacting with a wallet, users can see the defined post conditions associated with their transaction.

An important thing to note is then when a transaction fails to meet the supplied post conditions, it aborts but this is not immediately obvious until the transaction itself is aborted. STX are still spent broadcasting the transaction, but the transfer of any asset will fail.

Below is an example where post conditions are passed into a broadcasted transaction, utilizing the stacks.js library.

 
 
 const stxConditionCode = FungibleConditionCode.LessEqual;
 const stxConditionAmount = 50000000; // denoted in microstacks
 const postConditionAddressStx = stxAddress //recipient
 
 
 // converting to Clarity Values
 const sendStxFunctionArguments = [
   uintCV(50),
   standardPrincipalCV(stxAddress),
   standardPrincipalCV(stxRecipient)
 ];
 
 
 // defining the post condition as STX transfer
 const sendStxPostConditions = [
   makeStandardSTXPostCondition(
     postConditionAddressStx, //the sender
     stxConditionCode,
     stxConditionAmount
   )
 ];
 
 
 const sendStx = {
   contractAddress: stxAddress,
   contractName: contractName,
   functionName: 'send-stx',
   functionArgs: sendStxFunctionArguments,
   postConditions: sendStxPostConditions,
   senderKey: '<sender key>',
   network,
   anchorMode: AnchorMode.Any,
 };  const stxConditionCode = FungibleConditionCode.LessEqual;
 const stxConditionAmount = 50000000; // denoted in microstacks
 const postConditionAddressStx = stxAddress //recipient
 
 
 
 
 const sendStxFunctionArguments = [
   uintCV(50),
   standardPrincipalCV(stxAddress),
   standardPrincipalCV(stxRecipient)
 ];
 
 
 const sendStxPostConditions = [
   makeStandardSTXPostCondition(
     postConditionAddressStx, //the sender
     stxConditionCode,
     stxConditionAmount
   )
 ];
 
 
 const sendStx = {
   contractAddress: stxAddress,
   contractName: contractName,
   functionName: 'send-stx',
   functionArgs: sendStxFunctionArguments,
   postConditions: sendStxPostConditions,
   senderKey: <sender key>,
   network,
   anchorMode: AnchorMode.Any,
 };
 
 
 //Transaction Call
 const sendStxTransaction = async () => {
   try {
     const response = await makeContractCall(sendStx);
     const broadcastResponse = await broadcastTransaction(response, network);
     const txId = broadcastResponse.txid;
     console.log(txId)
     setBroadcastResponse(broadcastResponse)
   } catch (e) {
     console.log(e)
   }
 };
 
 

Solidity Revert()

In contrast, revert is a function built into Solidity that controls the flow of operations being performed within a smart contracts. When conditions within the contract interaction are being checked for validity, revert can be used to throw an exception when the necessary conditions for a call are not met. State changes are reverted(!) and the attempted contract interaction fails.

Revert adds the ability for developers to return an error message or predefined error type when state changes are rolled back. Additionally, revert allows for the remaining gas fees to be refunded when error is encountered.

Below is an example revert being used in a basic Solidity contract that implements a simple auction example. You can see revert being used to enforce certain conditions in a function that defines a "bidding" event.

Bid on the auction with the value sent
   /// together with this transaction.
   /// The value will only be refunded if the
   /// auction is not won.
   function bid() external payable {
       // Revert the call if the bidding
       // period is over.
       if (block.timestamp > auctionEndTime)
           revert("Auction already ended.");
 
 
       // If the bid is not higher, send the
       // money back (the `revert` will refund all changes in this transaction including
       // gas spent).
       if (msg.value <= highestBid)
           revert("There already is a higher bid.");
 
 
       if (highestBid != 0) {
           // Sending back the money by simply using
           // highestBidder.send(highestBid) is a security risk
           // because it can be prevented by the caller by e.g. spending
           // all gas on the transaction. It's safer to let them
           // withdraw the money themselves.
           payable(highestBidder).transfer(highestBid);
       }
 
 
       highestBidder = msg.sender;
       highestBid = msg.value;
       emit HighestBidIncreased(msg.sender, msg.value);
   }
 
 

Conclusion

While both Stacks Post Conditions and Solidity's revert are both designed to prevent unexpected behavior and allow for greater contract security, the implementation and use of each feature are very different.

Post conditions are built into the blockchain itself, while revert is written into smart contracts as they are developed.

Links

Great intro to post conditions with link to a more detailed example by Kenny @ the stack foundation Stacks Post Conditions (opens in a new tab)

Link the SIP-005 that outlines post conditions Stacks SIP-005 (opens in a new tab)