Create an account


Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tut] Solidity by Example – Simple Open Auction (Explained)

#1
Solidity by Example – Simple Open Auction (Explained)

<div>
<div class="kk-star-ratings kksr-auto kksr-align-left kksr-valign-top" data-payload="{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;651290&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;top&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;1&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;5&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;5&quot;,&quot;greet&quot;:&quot;Rate this post&quot;,&quot;legend&quot;:&quot;5\/5 - (1 vote)&quot;,&quot;size&quot;:&quot;24&quot;,&quot;width&quot;:&quot;142.5&quot;,&quot;_legend&quot;:&quot;{score}\/{best} - ({count} {votes})&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}">
<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-by-example-part-12-simple-open-auction/"><img src="https://blog.finxter.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=https%3A%2F%2Fi.ytimg.com%2Fvi%2FaHFRws9Deq8%2Fhqdefault.jpg" alt="YouTube Video"></a><figcaption></figcaption></figure>
<p>This article continues on the series we started the last time: <a href="https://blog.finxter.com/how-does-the-solidity-voting-smart-contract-work/" data-type="URL" data-id="https://blog.finxter.com/how-does-the-solidity-voting-smart-contract-work/" target="_blank" rel="noreferrer noopener">Solidity smart contract examples</a>, which implement a simplified real-world process. </p>
<p>Here, we’re walking through an example of a simple open auction.</p>
<p class="has-base-background-color has-background"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f30d.png" alt="?" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Original Source Code</strong>: <a rel="noreferrer noopener" href="https://docs.soliditylang.org/en/v0.8.15/solidity-by-example.html#simple-open-auction" data-type="URL" data-id="https://docs.soliditylang.org/en/v0.8.15/solidity-by-example.html#simple-open-auction" target="_blank">Solidity Docs</a></p>
<p>We’ll first lay out the entire <a href="https://blog.finxter.com/introduction-to-smart-contracts-and-solidity-part-3-blockchain-basics/" data-type="post" data-id="537705" target="_blank" rel="noreferrer noopener">smart contract</a> example without the comments for readability and development purposes. </p>
<p>Then we’ll dissect it part by part, analyze it and explain it. </p>
<p>Following this path, we’ll get a hands-on experience with smart contracts, as well as good practices in coding, understanding, and <a href="https://blog.finxter.com/top-8-scary-smart-contract-hacks-that-exploit-your-dapp-video/" data-type="post" data-id="437387" target="_blank" rel="noreferrer noopener">debugging</a> smart contracts.</p>
<h2><a></a>Smart contract – Simple Open Auction</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 SimpleAuction { address payable public beneficiary; uint public auctionEndTime; address public highestBidder; uint public highestBid; mapping(address => uint) pendingReturns; bool ended; event HighestBidIncreased(address bidder, uint amount); event AuctionEnded(address winner, uint amount); error AuctionAlreadyEnded(); error BidNotHighEnough(uint highestBid); error AuctionNotYetEnded(uint timeToAuctionEnd); error AuctionEndAlreadyCalled(); constructor( uint biddingTime, address payable beneficiaryAddress ) { beneficiary = beneficiaryAddress; auctionEndTime = block.timestamp + biddingTime; } function bid() external payable { if (block.timestamp > auctionEndTime) revert AuctionAlreadyEnded(); if (msg.value &lt;= highestBid) revert BidNotHighEnough(highestBid); if (highestBid != 0) { pendingReturns[highestBidder] += highestBid; } highestBidder = msg.sender; highestBid = msg.value; emit HighestBidIncreased(msg.sender, msg.value); } function withdraw() external returns (bool) { uint amount = pendingReturns[msg.sender]; if (amount > 0) { pendingReturns[msg.sender] = 0; if (!payable(msg.sender).send(amount)) { pendingReturns[msg.sender] = amount; return false; } } return true; } function auctionEnd() external { if (block.timestamp &lt; auctionEndTime) revert AuctionNotYetEnded(auctionEndTime - block.timestamp); if (ended) revert AuctionEndAlreadyCalled(); ended = true; emit AuctionEnded(highestBidder, highestBid); beneficiary.transfer(highestBid); }
}
</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</pre>
<p>Compiles only with Solidity compiler version 0.8.4 and later, but before version 0.9.</p>
<p class="has-base-background-color has-background"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f30d.png" alt="?" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Learn More</strong>: <a href="https://blog.finxter.com/layout-of-a-solidity-source-file/" data-type="post" data-id="455693">Layout of a Solidity File</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="">pragma solidity ^0.8.4; contract SimpleAuction {
</pre>
<p>Parameters of the auction are variables <code>beneficiary</code> and <code>auctionEndTime</code> which we’ll initialize with contract creation arguments while the contract gets <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">created</a>, i.e. in the contract constructor. </p>
<p>Data type for time variables is unsigned integer <code>uint</code>, so that we can represent either absolute Unix timestamps (seconds since 1970-01-01) or time periods in seconds (seconds lapsed from the reference moment we chose).</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=""> address payable public beneficiary; uint public auctionEndTime;
</pre>
<p>The current state of the auction is reflected in two variables, <code>highestBidder</code> and <code>highestBid</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=""> address public highestBidder; uint public highestBid;
</pre>
<p>Previous bids can be withdrawn, that’s why we have mapping data structure to record <code>pendingReturns</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=""> mapping(address => uint) pendingReturns;</pre>
<p>Indicator flag variable for the auction end. By default, the flag is initialized to <code>false</code>; we’ll prevent changing it once it switches to <code>true</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=""> bool ended;</pre>
<p>When changes occur, we want our smart contract to <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">emit</a> the corresponding change events.</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 HighestBidIncreased(address bidder, uint amount); event AuctionEnded(address winner, uint amount);
</pre>
<p>We’re defining four errors to describe relevant failures. Along with these errors, we’ll also introduce “triple-slash” comments, commonly known as <code>natspec</code> comments. They enable users to see comments when an error is displayed or when users are asked to confirm the transaction. </p>
<p class="has-base-background-color has-background"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f30d.png" alt="?" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Learn More</strong>: Natspec comments are formally defined in <a rel="noreferrer noopener" href="https://docs.soliditylang.org/en/develop/natspec-format.html" data-type="URL" data-id="https://docs.soliditylang.org/en/develop/natspec-format.html" target="_blank">Ethereum Natural Language Specification Format</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=""> /// The auction has already ended. error AuctionAlreadyEnded(); /// There is already a higher or equal bid. error BidNotHighEnough(uint highestBid); /// The auction has not ended yet, the remaining seconds are displayed. error AuctionNotYetEnded(uint timeToAuctionEnd); /// The function auctionEnd has already been called. error AuctionEndAlreadyCalled();
</pre>
<p>Initialization of the contract with the contract creation arguments <code>biddingTime</code> and <code>beneficiaryAddress</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=""> /// Create a simple auction with `biddingTime` /// seconds bidding time on behalf of the /// beneficiary address `beneficiaryAddress`. constructor( uint biddingTime, address payable beneficiaryAddress ) { beneficiary = beneficiaryAddress; auctionEndTime = block.timestamp + biddingTime; }
</pre>
<p>A bidder bids by sending the currency (<a href="https://blog.finxter.com/what-is-payable-in-solidity/" data-type="post" data-id="37282" target="_blank" rel="noreferrer noopener">pa</a>y<a href="https://blog.finxter.com/what-is-payable-in-solidity/" data-type="post" data-id="37282" target="_blank" rel="noreferrer noopener">ing</a>) to the smart contract representing the beneficiary, hence the <code>bid()</code> function is defined 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>.</p>
<p class="has-base-background-color has-background"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f30d.png" alt="?" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Learn More</strong>: <a href="https://blog.finxter.com/what-is-payable-in-solidity/" data-type="post" data-id="37282">What is <code>payable</code> in Solidity?</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=""> /// 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 {
</pre>
<p>The function call reverts if the bidding period ended.</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 (block.timestamp > auctionEndTime) revert AuctionAlreadyEnded();
</pre>
<p>The function rolls back the transaction to the bidder if the bid does not exceed the highest one.</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 (msg.value &lt;= highestBid) revert BidNotHighEnough(highestBid);
</pre>
<p>The previous highest bidder was outbid and his bid is added to his previous bids reserved for a refund. </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;" /> A direct refund is considered a <a href="https://blog.finxter.com/top-8-scary-smart-contract-hacks-that-exploit-your-dapp-video/" data-type="post" data-id="437387">secu</a><a rel="noreferrer noopener" href="https://blog.finxter.com/top-8-scary-smart-contract-hacks-that-exploit-your-dapp-video/" data-type="post" data-id="437387" target="_blank">r</a><a href="https://blog.finxter.com/top-8-scary-smart-contract-hacks-that-exploit-your-dapp-video/" data-type="post" data-id="437387">ity risk</a> due to the possibility of executing an untrusted contract. </p>
<p>Instead, the bidders (recipients) will withdraw their bids themselves by using withdraw() function below.</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 (highestBid != 0) { pendingReturns[highestBidder] += highestBid; }
</pre>
<p>The new highest bidder and his bid are recorded; the event <code>HighestBidIncreased</code> is emitted carrying this information pair.</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=""> highestBidder = msg.sender; highestBid = msg.value; emit HighestBidIncreased(msg.sender, msg.value); }
</pre>
<p>Bidders call the <code>withdraw()</code> function to retrieve the amount they bid.</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=""> /// Withdraw a bid that was overbid. function withdraw() external returns (bool) { uint amount = pendingReturns[msg.sender]; if (amount > 0) {
</pre>
<p>It is possible to call the <code>withdraw()</code> function again before the <code>send()</code> function returns. That’s the reason why we need to disable multiple sequential withdrawals from the same sender by setting the pending returns for a sender to 0.</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=""> pendingReturns[msg.sender] = 0;</pre>
<p>Variable type of <code>msg.sender</code> is not address <code>payable</code>, therefore we need to convert it explicitly by using function payable() as a wrapping function. </p>
<p>If the <code>send()</code> function ends with an error, we’ll just reset the pending amount and return <code>false</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=""> if (!payable(msg.sender).send(amount)) { // No need to call throw here, just reset the amount owing pendingReturns[msg.sender] = amount; return false; } } return true; }
</pre>
<p>The <code>auctionEnd()</code> function ends the auction and sends the highest bid to the beneficiary. </p>
<p>The official Solidity documentation recommends dividing the interacting functions into three functional parts:</p>
<ul>
<li>checking the conditions,</li>
<li>performing the actions, and</li>
<li>interacting with other contracts.</li>
</ul>
<p>Otherwise, by combining these parts rather than keeping them separated, more than one calling contract could try and modify the state of the called contract and change the called contract’s 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=""> /// End the auction and send the highest bid /// to the beneficiary. function auctionEnd() external {
</pre>
<p>Checking the conditions…</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 (block.timestamp &lt; auctionEndTime) revert AuctionNotYetEnded(auctionEndTime - block.timestamp); if (ended) revert AuctionEndAlreadyCalled();
</pre>
<p>…performing the actions…</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=""> ended = true; emit AuctionEnded(highestBidder, highestBid);
</pre>
<p>…and interacting with other contracts.</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=""> beneficiary.transfer(highestBid); }
}
</pre>
<p>Our smart contract example is a simple, but a powerful one, enabling us to bid an amount of currency to the beneficiary. </p>
<p>When the contract instantiates via its constructor, it sets the auction end time and its beneficiary, i.e. beneficiary address. </p>
<p>The contract has three simple features, implemented via dedicated functions: bidding, withdrawing the bids and ending the auction.</p>
<p>A new bid is accepted only if its amount is strictly larger than the current highest bid. A new bid acceptance means that the current highest bid is added to the bidder’s balance for later withdrawal. The new highest bidder becomes the current highest bidder and the new highest bid becomes the current highest bid.</p>
<p>Bid withdrawing returns all summed previous bids to each bidder (<code>mapping pendingReturns</code>).</p>
<h2><a></a>Contract Test Scenario</h2>
<p>Open auction duration (in seconds): <code>240</code></p>
<p>Beneficiary: <code>0x5B38Da6a701c568545dCfcB03FcB875f56beddC4</code></p>
<p>Testing/demonstration steps:</p>
<ol type="1">
<li><code>Account 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 bids 10 Wei;</code></li>
<li><code>Account 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db bids 25 Wei;</code></li>
<li><code>Account 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB bids 25 Wei (rejected);</code></li>
<li><code>Account 0x617F2E2fD72FD9D5503197092aC168c91465E7f2 bids 35 Wei;</code></li>
<li><code>Account 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 bids 40 Wei + initiates premature auction end;</code></li>
<li><code>Account 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 withdraws his bids;</code></li>
<li><code>Account 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db withdraws his bids;</code></li>
<li><code>Account 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB withdraws his bids;</code></li>
<li><code>Account 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB initiates timely auction end;</code></li>
<li><code>Account 0x617F2E2fD72FD9D5503197092aC168c91465E7f2 withdraws his bids;</code></li>
</ol>
<h2><a></a>Appendix – The Contract Arguments</h2>
<p>In this section is 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 arguments are the <em>open auction duration</em> (in seconds) and the beneficiary address (copy this line when deploying the example):</p>
<pre class="wp-block-preformatted"><code>300, 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4</code></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>Info</strong>: we could’ve used any amount of time, but I went with 300 seconds to timely simulate both a rejected attempt of ending the auction and the successful ending of the auction.</p>
<h2><a></a>Conclusion</h2>
<p>We continued our smart contract example series with this article that implements a simple open auction.</p>
<p>First, we laid out clean source code (without any comments) for readability purposes. Omitting the comments is not recommended, but we love living on the edge – and trying to be funny! <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f600.png" alt="?" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>Second, we dissected the code, analyzed it, and explained each possibly non-trivial segment. Just because we’re terrific, safe players who never risk it and do everything by the book <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f642.png" alt="?" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<hr class="wp-block-separator has-alpha-channel-opacity"/>
<div class="wp-block-image">
<figure class="aligncenter size-full"><a href="https://academy.finxter.com/university/solidity-basics/" target="_blank" rel="noopener"><img loading="lazy" width="363" height="650" src="https://blog.finxter.com/wp-content/uploads/2022/05/image-308.png" alt="" class="wp-image-387313" srcset="https://blog.finxter.com/wp-content/uploads/2022/05/image-308.png 363w, https://blog.finxter.com/wp-content/uplo...68x300.png 168w" sizes="(max-width: 363px) 100vw, 363px" /></a></figure>
</div>
<h2>Programmer Humor – Blockchain</h2>
<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" width="280" height="394" src="https://blog.finxter.com/wp-content/uploads/2022/07/image-31.png" alt="" class="wp-image-457795" srcset="https://blog.finxter.com/wp-content/uploads/2022/07/image-31.png 280w, https://blog.finxter.com/wp-content/uplo...13x300.png 213w" sizes="(max-width: 280px) 100vw, 280px" /><figcaption><em>“Blockchains are like grappling hooks, in that it’s extremely cool when you encounter a problem for which they’re the right solution, but it happens way too rarely in real life.”</em> <strong>source </strong> – <a href="https://imgs.xkcd.com/comics/blockchain.png" data-type="URL" data-id="https://imgs.xkcd.com/comics/blockchain.png" target="_blank" rel="noreferrer noopener">xkcd</a></figcaption></figure>
</div>
</div>


https://www.sickgaming.net/blog/2022/09/...explained/
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

Forum software by © MyBB Theme © iAndrew 2016