github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/docs/how-to-guides/porting-solidity-to-gno.md (about)

     1  ---
     2  id: port-solidity-to-gno
     3  ---
     4  
     5  # Port a Solidity Contract to a Gno Realm
     6  
     7  
     8  ## Overview
     9  
    10  This guide shows you how to port a Solidity contract `Simple Auction` to a Gno Realm `auction.gno` with test cases (Test Driven Development (TDD) approach).
    11  
    12  You can check the Solidity contract in this [link](https://docs.soliditylang.org/en/latest/solidity-by-example.html#simple-open-auction), and here's the code for porting.
    13  
    14  ```solidity
    15  // SPDX-License-Identifier: GPL-3.0
    16  pragma solidity ^0.8.4;
    17  contract SimpleAuction {
    18      // Parameters of the auction. Times are either
    19      // absolute unix timestamps (seconds since 1970-01-01)
    20      // or time periods in seconds.
    21      address payable public beneficiary;
    22      uint public auctionEndTime;
    23  
    24      // Current state of the auction.
    25      address public highestBidder;
    26      uint public highestBid;
    27  
    28      // Allowed withdrawals of previous bids
    29      mapping(address => uint) pendingReturns;
    30  
    31      // Set to true at the end, disallows any change.
    32      // By default initialized to `false`.
    33      bool ended;
    34  
    35      // Events that will be emitted on changes.
    36      event HighestBidIncreased(address bidder, uint amount);
    37      event AuctionEnded(address winner, uint amount);
    38  
    39      // Errors that describe failures.
    40  
    41      // The triple-slash comments are so-called natspec
    42      // comments. They will be shown when the user
    43      // is asked to confirm a transaction or
    44      // when an error is displayed.
    45  
    46      /// The auction has already ended.
    47      error AuctionAlreadyEnded();
    48      /// There is already a higher or equal bid.
    49      error BidNotHighEnough(uint highestBid);
    50      /// The auction has not ended yet.
    51      error AuctionNotYetEnded();
    52      /// The function auctionEnd has already been called.
    53      error AuctionEndAlreadyCalled();
    54  
    55      /// Create a simple auction with `biddingTime`
    56      /// seconds bidding time on behalf of the
    57      /// beneficiary address `beneficiaryAddress`.
    58      constructor(
    59          uint biddingTime,
    60          address payable beneficiaryAddress
    61      ) {
    62          beneficiary = beneficiaryAddress;
    63          auctionEndTime = block.timestamp + biddingTime;
    64      }
    65  
    66      /// Bid on the auction with the value sent
    67      /// together with this transaction.
    68      /// The value will only be refunded if the
    69      /// auction is not won.
    70      function bid() external payable {
    71          // No arguments are necessary, all
    72          // information is already part of
    73          // the transaction. The keyword payable
    74          // is required for the function to
    75          // be able to receive Ether.
    76  
    77          // Revert the call if the bidding
    78          // period is over.
    79          if (block.timestamp > auctionEndTime)
    80              revert AuctionAlreadyEnded();
    81  
    82          // If the bid is not higher, send the
    83          // money back (the revert statement
    84          // will revert all changes in this
    85          // function execution including
    86          // it having received the money).
    87          if (msg.value <= highestBid)
    88              revert BidNotHighEnough(highestBid);
    89  
    90          if (highestBid != 0) {
    91              // Sending back the money by simply using
    92              // highestBidder.send(highestBid) is a security risk
    93              // because it could execute an untrusted contract.
    94              // It is always safer to let the recipients
    95              // withdraw their money themselves.
    96              pendingReturns[highestBidder] += highestBid;
    97          }
    98          highestBidder = msg.sender;
    99          highestBid = msg.value;
   100          emit HighestBidIncreased(msg.sender, msg.value);
   101      }
   102  
   103      /// Withdraw a bid that was overbid.
   104      function withdraw() external returns (bool) {
   105          uint amount = pendingReturns[msg.sender];
   106          if (amount > 0) {
   107              // It is important to set this to zero because the recipient
   108              // can call this function again as part of the receiving call
   109              // before `send` returns.
   110              pendingReturns[msg.sender] = 0;
   111  
   112              // msg.sender is not of type `address payable` and must be
   113              // explicitly converted using `payable(msg.sender)` in order
   114              // use the member function `send()`.
   115              if (!payable(msg.sender).send(amount)) {
   116                  // No need to call throw here, just reset the amount owing
   117                  pendingReturns[msg.sender] = amount;
   118                  return false;
   119              }
   120          }
   121          return true;
   122      }
   123  
   124      /// End the auction and send the highest bid
   125      /// to the beneficiary.
   126      function auctionEnd() external {
   127          // It is a good guideline to structure functions that interact
   128          // with other contracts (i.e. they call functions or send Ether)
   129          // into three phases:
   130          // 1. checking conditions
   131          // 2. performing actions (potentially changing conditions)
   132          // 3. interacting with other contracts
   133          // If these phases are mixed up, the other contract could call
   134          // back into the current contract and modify the state or cause
   135          // effects (ether payout) to be performed multiple times.
   136          // If functions called internally include interaction with external
   137          // contracts, they also have to be considered interaction with
   138          // external contracts.
   139  
   140          // 1. Conditions
   141          if (block.timestamp < auctionEndTime)
   142              revert AuctionNotYetEnded();
   143          if (ended)
   144              revert AuctionEndAlreadyCalled();
   145  
   146          // 2. Effects
   147          ended = true;
   148          emit AuctionEnded(highestBidder, highestBid);
   149  
   150          // 3. Interaction
   151          beneficiary.transfer(highestBid);
   152      }
   153  }
   154  ```
   155  
   156  These are the basic concepts of the Simple Auction contract:
   157  
   158  * Everyone can send their bids during a bidding period.
   159  * The bids already include sending money / Ether in order to bind the bidders to their bids.
   160  * If the highest bid is raised, the previous highest bidder gets their money back.
   161  * After the end of the bidding period, the contract has to be called manually for the beneficiary to receive their money - contracts cannot activate themselves.
   162  
   163  The contract consists of:
   164  
   165  * A variable declaration
   166  * Initialization by a constructor
   167  * Three functions
   168  
   169  Let's dive into the details of the role of each function, and learn how to port each function into Gno with test cases.
   170  
   171  When writing a test case, the following conditions are often used to determine whether the function has been properly executed:
   172  
   173  * Value matching
   174  * Error status
   175  * Panic status
   176  
   177  Below is a test case helper that will help implement each condition.
   178  
   179  ### Gno - Testcase Helper
   180  
   181  [embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-1.gno go)
   182  ```go
   183  func shouldEqual(t *testing.T, got interface{}, expected interface{}) {
   184  	t.Helper()
   185  
   186  	if got != expected {
   187  		t.Errorf("expected %v(%T), got %v(%T)", expected, expected, got, got)
   188  	}
   189  }
   190  
   191  func shouldErr(t *testing.T, err error) {
   192  	t.Helper()
   193  	if err == nil {
   194  		t.Errorf("expected an error, but got nil.")
   195  	}
   196  }
   197  
   198  func shouldNoErr(t *testing.T, err error) {
   199  	t.Helper()
   200  	if err != nil {
   201  		t.Errorf("expected no error, but got err: %s.", err.Error())
   202  	}
   203  }
   204  
   205  func shouldPanic(t *testing.T, f func()) {
   206  	defer func() {
   207  		if r := recover(); r == nil {
   208  			t.Errorf("should have panic")
   209  		}
   210  	}()
   211  	f()
   212  }
   213  
   214  func shouldNoPanic(t *testing.T, f func()) {
   215  	defer func() {
   216  		if r := recover(); r != nil {
   217  			t.Errorf("should not have panic")
   218  		}
   219  	}()
   220  	f()
   221  }
   222  ```
   223  
   224  ## Variable init - Solidity
   225  
   226  [embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-2.sol solidity)
   227  ```solidity
   228  // Parameters of the auction. Times are either
   229  // absolute unix timestamps (seconds since 1970-01-01)
   230  // or time periods in seconds.
   231  address payable public beneficiary;
   232  uint public auctionEndTime;
   233  
   234  // Current state of the auction.
   235  address public highestBidder;
   236  uint public highestBid;
   237  
   238  // Allowed withdrawals of previous bids
   239  mapping(address => uint) pendingReturns;
   240  
   241  // Set to true at the end, disallows any change.
   242  // By default initialized to `false`.
   243  bool ended;
   244  
   245  // Events that will be emitted on changes.
   246  event HighestBidIncreased(address bidder, uint amount);
   247  event AuctionEnded(address winner, uint amount);
   248  
   249  // Errors that describe failures.
   250  
   251  // The triple-slash comments are so-called natspec
   252  // comments. They will be shown when the user
   253  // is asked to confirm a transaction or
   254  // when an error is displayed.
   255  
   256  /// The auction has already ended.
   257  error AuctionAlreadyEnded();
   258  /// There is already a higher or equal bid.
   259  error BidNotHighEnough(uint highestBid);
   260  /// The auction has not ended yet.
   261  error AuctionNotYetEnded();
   262  /// The function auctionEnd has already been called.
   263  error AuctionEndAlreadyCalled();
   264  
   265  /// Create a simple auction with `biddingTime`
   266  /// seconds bidding time on behalf of the
   267  /// beneficiary address `beneficiaryAddress`.
   268  constructor(
   269      uint biddingTime,
   270      address payable beneficiaryAddress
   271  ) {
   272      beneficiary = beneficiaryAddress;
   273      auctionEndTime = block.timestamp + biddingTime;
   274  }
   275  ```
   276  
   277  * `address payable public beneficiary;` : Address to receive the amount after the auction's ending.
   278  * `uint public auctionEndTime;` : Auction ending time.
   279  * `address public highestBidder;` : The highest bidder.
   280  * `uint public highestBid;` : The highest bid.
   281  * `mapping(address => uint) pendingReturns;` : Bidder's address and amount to be returned (in case of the highest bid changes).
   282  * `bool ended;` : Whether the auction is closed.
   283  
   284  ### Variable init - Gno
   285  
   286  [embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-3.gno go)
   287  ```go
   288  var (
   289  	receiver        = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
   290  	auctionEndBlock = std.GetHeight() + uint(300) // in blocks
   291  	highestBidder   std.Address
   292  	highestBid      = uint(0)
   293  	pendingReturns  avl.Tree
   294  	ended           = false
   295  )
   296  ```
   297  
   298  > **Note:** In Solidity, the Auction ending time is set by a time basis, but in the above case, it's set by a block basis.
   299  
   300  ###
   301  
   302  ## bid() - Solidity
   303  
   304  [embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-4.sol solidity)
   305  ```solidity
   306  function bid() external payable {
   307      // No arguments are necessary, all
   308      // information is already part of
   309      // the transaction. The keyword payable
   310      // is required for the function to
   311      // be able to receive Ether.
   312  
   313      // Revert the call if the bidding
   314      // period is over.
   315      if (block.timestamp > auctionEndTime)
   316          revert AuctionAlreadyEnded();
   317  
   318      // If the bid is not higher, send the
   319      // money back (the revert statement
   320      // will revert all changes in this
   321      // function execution including
   322      // it having received the money).
   323      if (msg.value <= highestBid)
   324          revert BidNotHighEnough(highestBid);
   325  
   326      if (highestBid != 0) {
   327          // Sending back the money by simply using
   328          // highestBidder.send(highestBid) is a security risk
   329          // because it could execute an untrusted contract.
   330          // It is always safer to let the recipients
   331          // withdraw their money themselves.
   332          pendingReturns[highestBidder] += highestBid;
   333      }
   334      highestBidder = msg.sender;
   335      highestBid = msg.value;
   336      emit HighestBidIncreased(msg.sender, msg.value);
   337  }
   338  ```
   339  
   340  `bid()` function is for participating in an auction and includes:
   341  
   342  * Determining whether an auction is closed.
   343  * Comparing a new bid with the current highest bid.
   344  * Prepare data to return the bid amount to the existing highest bidder in case of the highest bid is increased.
   345  * Update variables with the top bidder & top bid amount.
   346  
   347  ### bid() - Gno
   348  
   349  [embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-5.gno go)
   350  ```go
   351  func Bid() {
   352  	if std.GetHeight() > auctionEndBlock {
   353  		panic("Exceeded auction end block")
   354  	}
   355  
   356  	sentCoins := std.GetOrigSend()
   357  	if len(sentCoins) != 1 {
   358  		panic("Send only one type of coin")
   359  	}
   360  
   361  	sentAmount := uint(sentCoins[0].Amount)
   362  	if sentAmount <= highestBid {
   363  		panic("Too few coins sent")
   364  	}
   365  
   366  	// A new bid is higher than the current highest bid
   367  	if sentAmount > highestBid {
   368  		// If the highest bid is greater than 0,
   369  		if highestBid > 0 {
   370  			// Need to return the bid amount to the existing highest bidder
   371  			// Create an AVL tree and save
   372  			pendingReturns.Set(highestBidder.String(), highestBid)
   373  		}
   374  
   375  		// Update the top bidder address
   376  		highestBidder = std.GetOrigCaller()
   377  		// Update the top bid amount
   378  		highestBid = sentAmount
   379  	}
   380  }
   381  ```
   382  
   383  ### bid() - Gno Testcase
   384  
   385  [embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-6.gno go)
   386  ```go
   387  // Bid Function Test - Send Coin
   388  func TestBidCoins(t *testing.T) {
   389  	// Sending two types of coins
   390  	std.TestSetOrigCaller(bidder01)
   391  	std.TestSetOrigSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil)
   392  	shouldPanic(t, Bid)
   393  
   394  	// Sending lower amount than the current highest bid
   395  	std.TestSetOrigCaller(bidder01)
   396  	std.TestSetOrigSend(std.Coins{{"ugnot", 0}}, nil)
   397  	shouldPanic(t, Bid)
   398  
   399  	// Sending more amount than the current highest bid (exceeded)
   400  	std.TestSetOrigCaller(bidder01)
   401  	std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil)
   402  	shouldNoPanic(t, Bid)
   403  }
   404  
   405  // Bid Function Test - Bid by two or more people
   406  func TestBidCoins(t *testing.T) {
   407  	// bidder01 bidding with 1 coin
   408  	std.TestSetOrigCaller(bidder01)
   409  	std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil)
   410  	shouldNoPanic(t, Bid)
   411  	shouldEqual(t, highestBid, 1)
   412  	shouldEqual(t, highestBidder, bidder01)
   413  	shouldEqual(t, pendingReturns.Size(), 0)
   414  
   415  	// bidder02 bidding with 1 coin
   416  	std.TestSetOrigCaller(bidder02)
   417  	std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil)
   418  	shouldPanic(t, Bid)
   419  
   420  	// bidder02 bidding with 2 coins
   421  	std.TestSetOrigCaller(bidder02)
   422  	std.TestSetOrigSend(std.Coins{{"ugnot", 2}}, nil)
   423  	shouldNoPanic(t, Bid)
   424  	shouldEqual(t, highestBid, 2)
   425  	shouldEqual(t, highestBidder, bidder02)
   426  	shouldEqual(t, pendingReturns.Size(), 1)
   427  }
   428  ```
   429  
   430  ###
   431  
   432  ## withdraw() - Solidity
   433  
   434  [embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-7.sol solidity)
   435  ```solidity
   436  /// Withdraw a bid that was overbid.
   437  function withdraw() external returns (bool) {
   438      uint amount = pendingReturns[msg.sender];
   439      if (amount > 0) {
   440          // It is important to set this to zero because the recipient
   441          // can call this function again as part of the receiving call
   442          // before `send` returns.
   443          pendingReturns[msg.sender] = 0;
   444  
   445          // msg.sender is not of type `address payable` and must be
   446          // explicitly converted using `payable(msg.sender)` in order
   447          // use the member function `send()`.
   448          if (!payable(msg.sender).send(amount)) {
   449              // No need to call throw here, just reset the amount owing
   450              pendingReturns[msg.sender] = amount;
   451              return false;
   452          }
   453      }
   454      return true;
   455  }
   456  ```
   457  
   458  `withdraw()` is to return the bid amount to the existing highest bidder in case of the highest bid changes and includes:
   459  
   460  * When called, determine if there's a bid amount to be returned to the address.
   461  * (If there's an amount to be returned) Before returning, set the previously recorded amount to `0` and return the actual amount.
   462  
   463  ### withdraw() - Gno
   464  
   465  [embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-8.gno go)
   466  ```go
   467  func Withdraw() {
   468  	// Query the return amount to non-highest bidders
   469  	amount, _ := pendingReturns.Get(std.GetOrigCaller().String())
   470  
   471  	if amount > 0 {
   472  		// If there's an amount, reset the amount first,
   473  		pendingReturns.Set(std.GetOrigCaller().String(), 0)
   474  
   475  		// Return the exceeded amount
   476  		banker := std.GetBanker(std.BankerTypeRealmSend)
   477  		pkgAddr := std.GetOrigPkgAddr()
   478  
   479  		banker.SendCoins(pkgAddr, std.GetOrigCaller(), std.Coins{{"ugnot", amount.(int64)}})
   480  	}
   481  }
   482  ```
   483  
   484  ###
   485  
   486  ### withdraw() - Gno Testcase
   487  
   488  [embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-9.gno go)
   489  ```go
   490  // Withdraw Function Test
   491  func TestWithdraw(t *testing.T) {
   492  	// If there's no participants for return
   493  	shouldEqual(t, pendingReturns.Size(), 0)
   494  
   495  	// If there's participants for return (data generation
   496  	returnAddr := bidder01.String()
   497  	returnAmount := int64(3)
   498  	pendingReturns.Set(returnAddr, returnAmount)
   499  	shouldEqual(t, pendingReturns.Size(), 1)
   500  	shouldEqual(t, pendingReturns.Has(returnAddr), true)
   501  
   502  	banker := std.GetBanker(std.BankerTypeRealmSend)
   503  	pkgAddr := std.GetOrigPkgAddr()
   504  	banker.SendCoins(pkgAddr, std.Address(returnAddr), std.Coins{{"ugnot", returnAmount}})
   505  	shouldEqual(t, banker.GetCoins(std.Address(returnAddr)).String(), "3ugnot")
   506  }
   507  ```
   508  
   509  ## auctionEnd() - Solidity
   510  
   511  [embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-10.sol solidity)
   512  ```solidity
   513  /// End the auction and send the highest bid
   514  /// to the beneficiary.
   515  function auctionEnd() external {
   516      // It is a good guideline to structure functions that interact
   517      // with other contracts (i.e. they call functions or send Ether)
   518      // into three phases:
   519      // 1. checking conditions
   520      // 2. performing actions (potentially changing conditions)
   521      // 3. interacting with other contracts
   522      // If these phases are mixed up, the other contract could call
   523      // back into the current contract and modify the state or cause
   524      // effects (ether payout) to be performed multiple times.
   525      // If functions called internally include interaction with external
   526      // contracts, they also have to be considered interaction with
   527      // external contracts.
   528  
   529      // 1. Conditions
   530      if (block.timestamp < auctionEndTime)
   531          revert AuctionNotYetEnded();
   532      if (ended)
   533          revert AuctionEndAlreadyCalled();
   534  
   535      // 2. Effects
   536      ended = true;
   537      emit AuctionEnded(highestBidder, highestBid);
   538  
   539      // 3. Interaction
   540      beneficiary.transfer(highestBid);
   541  }
   542  ```
   543  
   544  `auctionEnd()` function is for ending the auction and includes:
   545  
   546  * Determines if the auction should end by comparing the end time.
   547  * Determines if the auction has already ended or not.
   548    * (If not ended) End the auction.
   549    * (If not ended) Send the highest bid amount to the recipient.
   550  
   551  ### auctionEnd() - Gno
   552  
   553  [embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-11.gno go)
   554  ```go
   555  func AuctionEnd() {
   556  	if std.GetHeight() < auctionEndBlock {
   557  		panic("Auction hasn't ended")
   558  	}
   559  
   560  	if ended {
   561  		panic("Auction has ended")
   562  
   563  	}
   564  	ended = true
   565  
   566  	// Send the highest bid to the recipient
   567  	banker := std.GetBanker(std.BankerTypeRealmSend)
   568  	pkgAddr := std.GetOrigPkgAddr()
   569  
   570  	banker.SendCoins(pkgAddr, receiver, std.Coins{{"ugnot", int64(highestBid)}})
   571  }
   572  ```
   573  
   574  ### auctionEnd() - Gno Testcase
   575  
   576  [embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-12.gno go)
   577  ```go
   578  // AuctionEnd() Function Test
   579  func TestAuctionEnd(t *testing.T) {
   580  	// Auction is ongoing
   581  	shouldPanic(t, AuctionEnd)
   582  
   583  	// Auction ends
   584  	highestBid = 3
   585  	std.TestSkipHeights(500)
   586  	shouldNoPanic(t, AuctionEnd)
   587  	shouldEqual(t, ended, true)
   588  
   589  	banker := std.GetBanker(std.BankerTypeRealmSend)
   590  	shouldEqual(t, banker.GetCoins(receiver).String(), "3ugnot")
   591  
   592  	// Auction has already ended
   593  	shouldPanic(t, AuctionEnd)
   594  	shouldEqual(t, ended, true)
   595  }
   596  ```
   597  
   598  ## Precautions for Running Test Cases
   599  
   600  * Each test function should be executed separately one by one, to return all passes without any errors.
   601  * Same as Go, Gno doesn't support `setup()` & `teardown()` functions. So running two or more test functions simultaneously can result in tainted data.
   602  * If you want to do the whole test at once, make it into a single function as below:
   603  
   604  [embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-13.gno go)
   605  ```go
   606  // The whole test
   607  func TestFull(t *testing.T) {
   608  	bidder01 := testutils.TestAddress("bidder01") // g1vf5kger9wgcrzh6lta047h6lta047h6lufftkw
   609  	bidder02 := testutils.TestAddress("bidder02") // g1vf5kger9wgcryh6lta047h6lta047h6lnhe2x2
   610  
   611  	// Variables test
   612  	{
   613  		shouldEqual(t, highestBidder, "")
   614  		shouldEqual(t, receiver, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
   615  		shouldEqual(t, auctionEndBlock, 423)
   616  		shouldEqual(t, highestBid, 0)
   617  		shouldEqual(t, pendingReturns.Size(), 0)
   618  		shouldEqual(t, ended, false)
   619  	}
   620  
   621  	// Send two or more types of coins
   622  	{
   623  		std.TestSetOrigCaller(bidder01)
   624  		std.TestSetOrigSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil)
   625  		shouldPanic(t, Bid)
   626  	}
   627  
   628  	// Send less than the highest bid
   629  	{
   630  		std.TestSetOrigCaller(bidder01)
   631  		std.TestSetOrigSend(std.Coins{{"ugnot", 0}}, nil)
   632  		shouldPanic(t, Bid)
   633  	}
   634  
   635  	// Send more than the highest bid
   636  	{
   637  		std.TestSetOrigCaller(bidder01)
   638  		std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil)
   639  		shouldNoPanic(t, Bid)
   640  
   641  		shouldEqual(t, pendingReturns.Size(), 0)
   642  		shouldEqual(t, highestBid, 1)
   643  		shouldEqual(t, highestBidder, "g1vf5kger9wgcrzh6lta047h6lta047h6lufftkw")
   644  	}
   645  
   646  	// Other participants in the auction
   647  	{
   648  
   649  		// Send less amount than the current highest bid (current: 1)
   650  		std.TestSetOrigCaller(bidder02)
   651  		std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil)
   652  		shouldPanic(t, Bid)
   653  
   654  		// Send more amount than the current highest bid (exceeded)
   655  		std.TestSetOrigCaller(bidder02)
   656  		std.TestSetOrigSend(std.Coins{{"ugnot", 2}}, nil)
   657  		shouldNoPanic(t, Bid)
   658  
   659  		shouldEqual(t, highestBid, 2)
   660  		shouldEqual(t, highestBidder, "g1vf5kger9wgcryh6lta047h6lta047h6lnhe2x2")
   661  
   662  		shouldEqual(t, pendingReturns.Size(), 1) // Return to the existing bidder
   663  		shouldEqual(t, pendingReturns.Has("g1vf5kger9wgcrzh6lta047h6lta047h6lufftkw"), true)
   664  	}
   665  
   666  	// Auction ends
   667  	{
   668  		std.TestSkipHeights(150)
   669  		shouldPanic(t, AuctionEnd)
   670  		shouldEqual(t, ended, false)
   671  
   672  		std.TestSkipHeights(301)
   673  		shouldNoPanic(t, AuctionEnd)
   674  		shouldEqual(t, ended, true)
   675  
   676  		banker := std.GetBanker(std.BankerTypeRealmSend)
   677  		shouldEqual(t, banker.GetCoins(receiver).String(), "2ugnot")
   678  	}
   679  }
   680  ```