[Tut] Solidity Example – Safe Remote Purchase - Printable Version +- Sick Gaming (https://www.sickgaming.net) +-- Forum: Programming (https://www.sickgaming.net/forum-76.html) +--- Forum: Python (https://www.sickgaming.net/forum-83.html) +--- Thread: [Tut] Solidity Example – Safe Remote Purchase (/thread-99957.html) |
[Tut] Solidity Example – Safe Remote Purchase - xSicKxBot - 09-17-2022 Solidity Example – Safe Remote Purchase <div> <div class="kk-star-ratings kksr-auto kksr-align-left kksr-valign-top" data-payload="{"align":"left","id":"680392","slug":"default","valign":"top","ignore":"","reference":"auto","class":"","count":"1","readonly":"","score":"5","best":"5","gap":"5","greet":"Rate this post","legend":"5\/5 - (1 vote)","size":"24","width":"142.5","_legend":"{score}\/{best} - ({count} {votes})","font_factor":"1.25"}"> <div class="kksr-stars"> <div class="kksr-stars-inactive"> <div class="kksr-star" data-star="1" style="padding-right: 5px"> <div class="kksr-icon" style="width: 24px; height: 24px;"></div> </p></div> <div class="kksr-star" data-star="2" style="padding-right: 5px"> <div class="kksr-icon" style="width: 24px; height: 24px;"></div> </p></div> <div class="kksr-star" data-star="3" style="padding-right: 5px"> <div class="kksr-icon" style="width: 24px; height: 24px;"></div> </p></div> <div class="kksr-star" data-star="4" style="padding-right: 5px"> <div class="kksr-icon" style="width: 24px; height: 24px;"></div> </p></div> <div class="kksr-star" data-star="5" style="padding-right: 5px"> <div class="kksr-icon" style="width: 24px; height: 24px;"></div> </p></div> </p></div> <div class="kksr-stars-active" style="width: 142.5px;"> <div class="kksr-star" style="padding-right: 5px"> <div class="kksr-icon" style="width: 24px; height: 24px;"></div> </p></div> <div class="kksr-star" style="padding-right: 5px"> <div class="kksr-icon" style="width: 24px; height: 24px;"></div> </p></div> <div class="kksr-star" style="padding-right: 5px"> <div class="kksr-icon" style="width: 24px; height: 24px;"></div> </p></div> <div class="kksr-star" style="padding-right: 5px"> <div class="kksr-icon" style="width: 24px; height: 24px;"></div> </p></div> <div class="kksr-star" style="padding-right: 5px"> <div class="kksr-icon" style="width: 24px; height: 24px;"></div> </p></div> </p></div> </div> <div class="kksr-legend" style="font-size: 19.2px;"> 5/5 – (1 vote) </div> </div> <figure class="wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube"><a href="https://blog.finxter.com/solidity-example-safe-remote-purchase/"><img src="https://blog.finxter.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=https%3A%2F%2Fi.ytimg.com%2Fvi%2FSZ-agoVo67A%2Fhqdefault.jpg" alt="YouTube Video"></a><figcaption></figcaption></figure> <p>This article continues on the <a href="https://blog.finxter.com/top-solidity-smart-contract-examples-for-learning/" data-type="URL" data-id="https://blog.finxter.com/top-solidity-smart-contract-examples-for-learning/" target="_blank" rel="noreferrer noopener">Solidity Smart Contract Examples</a> series, which implements a simple, but the useful process of safe remote purchase. </p> <p>Here, we’re walking through an example of a blind auction (<a href="https://docs.soliditylang.org/en/v0.8.15/solidity-by-example.html#safe-remote-purchase" data-type="URL" data-id="https://docs.soliditylang.org/en/v0.8.15/solidity-by-example.html#safe-remote-purchase" target="_blank" rel="noreferrer noopener">docs<a href="https://docs.soliditylang.org/en/v0.8.15/solidity-by-example.html#safe-remote-purchase"></a></a>). </p> <ul> <li>We’ll first lay out the entire <a rel="noreferrer noopener" href="https://blog.finxter.com/introduction-to-smart-contracts-and-solidity-part-3-blockchain-basics/" data-type="post" data-id="537705" target="_blank">smart contract</a> example without the comments for readability and development purposes. </li> <li>Then we’ll dissect it part by part, analyze it and explain it. </li> <li>Following this path, we’ll get a hands-on experience with smart contracts, as well as good practices in coding, understanding, and debugging smart contracts.</li> </ul> <h2>Smart Contract – Safe Remote Purchase</h2> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.4; contract Purchase { uint public value; address payable public seller; address payable public buyer; enum State { Created, Locked, Release, Inactive } State public state; modifier condition(bool condition_) { require(condition_); _; } error OnlyBuyer(); error OnlySeller(); error InvalidState(); error ValueNotEven(); modifier onlyBuyer() { if (msg.sender != buyer) revert OnlyBuyer(); _; } modifier onlySeller() { if (msg.sender != seller) revert OnlySeller(); _; } modifier inState(State state_) { if (state != state_) revert InvalidState(); _; } event Aborted(); event PurchaseConfirmed(); event ItemReceived(); event SellerRefunded(); constructor() payable { seller = payable(msg.sender); value = msg.value / 2; if ((2 * value) != msg.value) revert ValueNotEven(); } function abort() external onlySeller inState(State.Created) { emit Aborted(); state = State.Inactive; seller.transfer(address(this).balance); } function confirmPurchase() external inState(State.Created) condition(msg.value == (2 * value)) payable { emit PurchaseConfirmed(); buyer = payable(msg.sender); state = State.Locked; } function confirmReceived() external onlyBuyer inState(State.Locked) { emit ItemReceived(); state = State.Release; buyer.transfer(value); } function refundSeller() external onlySeller inState(State.Release) { emit SellerRefunded(); state = State.Inactive; seller.transfer(3 * value); } } </pre> <h2><a></a>Code breakdown and analysis</h2> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.4; contract Purchase { </pre> <p>The state variables for recording the value, seller, and buyer addresses.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> uint public value; address payable public seller; address payable public buyer; </pre> <p>For the <a href="https://blog.finxter.com/solidity-crash-course/" data-type="post" data-id="445146" target="_blank" rel="noreferrer noopener">first time</a>, we’re introducing the <code>enum</code> data structure that symbolically defines the four possible states of our contract. The states are internally indexed from <code>0</code> to <code>enum_length - 1</code>.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> enum State { Created, Locked, Release, Inactive }</pre> <p>The variable state keeps track of the current state. Our contract starts by default in the created state and can transition to the Locked, Release, and Inactive state.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> State public state;</pre> <p>The <code>condition</code> modifier guards a function against executing without previously satisfying the condition, i.e. an expression given alongside the function definition.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> modifier condition(bool condition_) { require(condition_); _; } </pre> <p>The error definitions are used with the appropriate, equally-named modifiers.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> error OnlyBuyer(); error OnlySeller(); error InvalidState(); error ValueNotEven(); </pre> <p>The <code>onlyBuyer</code> modifier guards a function against executing when the function caller is not the buyer.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> modifier onlyBuyer() { if (msg.sender != buyer) revert OnlyBuyer(); _; } </pre> <p>The <code>onlySeller</code> modifier guards a function against executing when the function caller differs from the seller.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> modifier onlySeller() { if (msg.sender != seller) revert OnlySeller(); _; } </pre> <p>The <code>inState</code> modifier guards a function against executing when the contract state differs from the required <code>state_</code>.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> modifier inState(State state_) { if (state != state_) revert InvalidState(); _; } </pre> <p>The events that the contract emits to acknowledge the functions <code>abort()</code>, <code>confirmPurchase()</code>, <code>confirmReceived()</code>, and <code>refundSeller()</code> were executed.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> event Aborted(); event PurchaseConfirmed(); event ItemReceived(); event SellerRefunded(); </pre> <p>The constructor is declared as <code><a href="https://blog.finxter.com/what-is-payable-in-solidity/" data-type="post" data-id="37282" target="_blank" rel="noreferrer noopener">payable</a></code>, meaning that the contract deployment (synonyms <em>creation</em>, <em>instantiation</em>) requires sending a value (<code>msg.value</code>) with the contract-creating transaction.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> constructor() payable {</pre> <p>The <code>seller</code> state variable is set to <code>msg.sender</code> address, cast (converted) to payable.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> seller = payable(msg.sender);</pre> <p>The value state variable is set to half the <code>msg.value</code>, because both the seller and the buyer have to put twice the value of the item being sold/bought into the contract as an escrow agreement.</p> <p class="has-global-color-8-background-color has-background"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f4a1.png" alt="?" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Info</strong>: <em>“Escrow is a legal arrangement in which a third party temporarily holds money or property until a particular condition has been met (such as the fulfillment of a purchase agreement).”</em> (<a rel="noreferrer noopener" href="https://www.rocketmortgage.com/learn/what-is-escrow" data-type="URL" data-id="https://www.rocketmortgage.com/learn/what-is-escrow" target="_blank">source</a>) </p> <p>In our case, our escrow is our smart contract.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> value = msg.value / 2;</pre> <p>If the value is not equally divided, i.e. the <code>msg.value</code> is not an even number, the function will terminate. Since the seller will always</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> if ((2 * value) != msg.value) revert ValueNotEven(); } </pre> <p>Aborting the remote safe purchase is allowed only in the <code>Created</code> state and only by the seller. </p> <p>The <code>external</code> keyword makes the function callable only by other accounts / smart contracts. From the business perspective, only the seller can call the <code>abort()</code> function and only before the buyer decides to purchase, i.e. before the contract enters the <code>Locked</code> state.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> function abort() external onlySeller inState(State.Created) { </pre> <p>Emits the <code>Aborted</code> event, the contract state transitions to inactive, and the balance is transferred to the seller.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> emit Aborted(); state = State.Inactive; </pre> <p class="has-global-color-8-background-color has-background"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f4a1.png" alt="?" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Note</strong>: <em>“Prior to version 0.5.0, Solidity allowed address members to be accessed by a contract instance, for example, this.balance. This is now forbidden and an explicit conversion to address must be done: address(this).balance.”</em> (<a href="https://docs.soliditylang.org/en/v0.8.15/units-and-global-variables.html">docs</a>). </p> <p>In other words, this keyword lets us access the contract’s inherited members. </p> <p>Every contract inherits its members from the address type and can access these members via <code>address(this).<a member></code> (<a href="https://docs.soliditylang.org/en/v0.8.15/units-and-global-variables.html#address-related" data-type="URL" data-id="https://docs.soliditylang.org/en/v0.8.15/units-and-global-variables.html#address-related" target="_blank" rel="noreferrer noopener">docs</a>).</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> seller.transfer(address(this).balance); } </pre> <p>The <code>confirmPurchase()</code> function is available for execution only in the <code>Created</code> state. </p> <p>It enforces the rule that a <code>msg.value</code> must be twice the value of the purchase. </p> <p>The <code>confirmPurchase()</code> function is also declared as <code>payable</code>, meaning the caller, i.e. the buyer has to send the currency (<code>msg.value</code>) with the function call.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> function confirmPurchase() external inState(State.Created) condition(msg.value == (2 * value)) payable { </pre> <p>The <a href="https://blog.finxter.com/ethereum-virtual-machine-evm-message-calls-solidity-smart-contracts/" data-type="post" data-id="592250" target="_blank" rel="noreferrer noopener">event</a> <code>PurchaseConfirmed()</code> is emitted to mark the purchase confirmation.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> emit PurchaseConfirmed();</pre> <p>The <code>msg.sender</code> value is cast to payable and assigned to the buyer variable.</p> <p class="has-global-color-8-background-color has-background"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f4a1.png" alt="?" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Info</strong>: Addresses are <em>non-payable</em> by design to prevent accidental payments; that’s why we have to cast an address to a payable before being able to transfer a payment.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> buyer = payable(msg.sender);</pre> <p>The state is set to <code>Locked</code> as seller and buyer entered the contract, i.e., our digital version of an escrow agreement.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> state = State.Locked; } </pre> <p>The <code>confirmReceived()</code> function is available for execution only in the <code>Locked</code> state, and only to the buyer. </p> <p>Since the buyer deposited twice the value amount and withdrew only a single value amount, the second value amount remains on the contract balance with the seller’s deposit.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> function confirmReceived() external onlyBuyer inState(State.Locked) { </pre> <p>Emits the <code>ItemReceived()</code> event.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> emit ItemReceived();</pre> <p>Changes the state to Release.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> state = State.Release;</pre> <p>Transfers the deposit to the buyer.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> buyer.transfer(value); } </pre> <p>The <code>refundSeller()</code> function is available for execution only in the <code>Release</code> state, and only to the seller. </p> <p>Since the seller deposited twice the value amount and earned a single value amount from the purchase, the contract transfers three value amounts from the contract balance to the seller.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> function refundSeller() external onlySeller inState(State.Release) { </pre> <p>Emits the <code>SellerRefunded()</code> event.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> emit SellerRefunded();</pre> <p>Changes the state to <code>Inactive</code>.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> state = State.Inactive;</pre> <p>Transfers the deposit of two value amounts and the one earned value amount to the seller.</p> <pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> seller.transfer(3 * value); } } </pre> <p>Our <a href="https://blog.finxter.com/top-solidity-smart-contract-examples-for-learning/" data-type="post" data-id="663675" target="_blank" rel="noreferrer noopener">smart contract example</a> of a safe remote purchase is a nice and simple example that demonstrates how a purchase may be conducted on the <a href="https://blog.finxter.com/smart-contracts-and-evm/" data-type="post" data-id="92507" target="_blank" rel="noreferrer noopener">Ethereum blockchain</a> network. </p> <p>The safe remote purchase example shows two parties, a seller and a buyer, who both enter a trading relationship with their deposits to the contract balance. </p> <p>Each deposit amounts to twice the value of the purchase, meaning that the contract balance will hold four times the purchase value at its highest point, i.e. in the <code>Locked</code> state.</p> <p>The height of deposits is intended to stimulate the resolution of any possible disputes between the parties, because otherwise, their deposits will stay locked and unavailable in the contract balance. </p> <p>When the buyer confirms that he received the goods he purchased, the contract will transition to the <code>Release</code> state, and the purchase value will be released to the buyer.</p> <p>The seller can now withdraw his earned purchase value with the deposit, the contract balance drops to 0 Wei, the contract transitions to the <code>Inactive</code> state, and the safe remote purchase concludes with execution.</p> <h2>The Contract Arguments</h2> <p>This section contains additional information for running the contract. We should expect that our example accounts may change with each refresh/reload of Remix.</p> <p>Our contract creation argument is the <em>deposit </em>(twice the purchase value). We’ll assume the purchase value to be 5 Wei, making the contract creation argument very simple:</p> <p><code>10</code></p> <h2><a></a>Contract Test Scenario</h2> <ol type="1"> <li>Account <code>0x5B38Da6a701c568545dCfcB03FcB875f56beddC4</code> deploys the contract with a deposit of 10 Wei, effectively becoming a seller.</li> <li>Account <code>0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2</code> confirms the purchase by calling the <code>confirmPurchase()</code> function and enters the trade with a deposit of 10 Wei, effectively becoming a buyer.</li> <li>The buyer confirms receiving the order by calling the <code>confirmReceived()</code> function.</li> <li>The seller concludes the trade by calling the <code>refundSeller()</code> function.</li> </ol> <h2><a></a>Conclusion</h2> <p>We continued our smart contract example series with this article that implements a safe remote purchase.</p> <p>First, we laid out clean source code (without any comments) for readability purposes.</p> <p>Second, we dissected the code, analyzed it, and explained each possibly non-trivial segment.</p> <hr class="wp-block-separator has-alpha-channel-opacity"/> <div class="wp-block-image"> <figure class="aligncenter size-full is-resized"><a href="https://academy.finxter.com/university/solidity-building-blocks-an-advanced-introduction-towards-smart-contract-development-on-ethereum/" target="_blank" rel="noopener"><img loading="lazy" src="https://blog.finxter.com/wp-content/uploads/2022/05/image-291.png" alt="" class="wp-image-387294" width="363" height="650" srcset="https://blog.finxter.com/wp-content/uploads/2022/05/image-291.png 363w, https://blog.finxter.com/wp-content/uploads/2022/05/image-291-168x300.png 168w" sizes="(max-width: 363px) 100vw, 363px" /></a></figure> </div> </div> https://www.sickgaming.net/blog/2022/09/15/solidity-example-safe-remote-purchase/ |