Creating a house swap contract using Solidity and Hardhat
Tags: ethereum, smartcontract, solidity, hardhat
Published: 2023-09-19 15:46:27

In this first part of this series, we are going to learn how to create an smart contract which allows two users to swap their houses. You can read the full contract code [here](https://github.com/icolomina/house-swap/blob/main/contracts/HouseSwap.sol)

## How the contract works
The following sections describes how the contract works without diving into the peculiarities of each method but showing an overview of the contract flow.

### Init the contract
The contract owner (the one who deploys it) call *initialize* function. That function sets contract status to *INITIALIZED* and stores house data so that other users can query it and make swap offers.
Only the contract owner can invoke this function.

### Add a new offer
A user can call *addOffer* function to send a swap offer. After called, a new event is emitted holding the offer. The owner cannot invoke this function since he does not swap his own house.

### Accept an offer
If the contract owner reads an offer and wants to accept it, he can call *acceptOffer* method. This method will change contract status to *ACCEPTED* and the contract will not accept more offers. Only contract owner can call this function.

### Confirm swap
After the contract owner accepts an offer, the *targetUser* can confirm the swap by calling *confirmSwap* function. This function has some conditions:

- If the offer specifies that the owner must pay some amount to the target, the owner will have to deposit enough ether so that the contract can transfer the required funds to the target. To do it, the owner can invoke the *deposit* function which is *payable*.

- The same applies to the target. If the offer specifies that the target must pay some amount to the owner, the target will also have to deposit enough ether. 

This function finishes changing the propietary of each house.

## The contract code
Now we have seen an overview about how the contract works, let's dive into the code.

### Events and storage vars

```solidity
event NewOffer(Offer offer);
event BalanceUpdated(address, uint256);

address payable owner;
address payable targetUser;
Swap swap;
Statuses status;
mapping (address => uint256) balances;
```
As we can see, these first lines declare two events and five variables. Let's describe them one by one:

- **event NewOffer**: Function *addOffer* emits this event after a user sends an offer. An *Offer* type variable is passed to the event (We will see the *Offer* type in the next section). 
- **event BalanceUpdated(address, uint256)**: Function *deposit* emits this event when *owner* or *targetUser* deposits funds to the contract address. This is necessary when the swap requires transferring an extra payment to origin or target. BalanceUpdated receives address which updates its balance and the amount.
- **address payable owner**: This variable stores the owner's address. The one who deploys the contract and wants to swap his house. This address needs to be payable so that contract can transfer funds to it if required.
- **address payable targetUser**: This variable stores the address of the user that made the accepted offer. This address also needs to be payable so that contract can transfer funds to it if required
- **Swap swap**: This variable stores the swap information (we will see *Swap* struct in the next section).
- **Statuses status**: This variable stores swap status. We will see the possible status in the next section where we will see the *Statuses* enum.
- **mapping (address => uint256) balances**: This mapping stores balances both from owner and target. It is necessary to transfer funds if swap requires it.

### Structs and enums

```solidity
enum Statuses {
    PENDING,
    INITIALIZED,
    ACCEPTED,
    FINISHED
}

struct Swap {
    House origin;
    House target;
    uint256 amountPayOriginToTarget;
    uint256 amountPayTargetToOrigin;
}

struct House {
    string houseType;
    uint value;
    string link;
    address propietary;
}

struct Offer {
    House house;
    address targetUser;
    uint256 amountPayOriginToTarget;
    uint256 amountPayTargetToOrigin;
}
```
The contract declares some structs which includes common information, and an enum . Let's see each of them:
- **enum Statuses**: Stores the possible status of the contract.
- **struct House**: Includes information about a house. This struct contains the following keys:
   - **houseType**: House type can be *duplex*, *apartment* etc
   - **uint value**: The value of the house. It can be the value in fiat or crypto. It does not matter since it is not used as a value to transfer.
   - **string link**: The site where users can search for more information about the house
   - **address propietary** : The address of the user who mades the offer or the owner address if the struct stores the owner house information.

- **struct Offer**: This struct stores an offer made by a user who wants to swap. This struct contains the following keys:
   - **House house**: The house that the user offers for swapping.
   - **address targetUser**: The address of the user who sends the offer.
   - **uint256 extraPayOriginToTarget**: A value greater than 0 indicates that owner has to pay an extra amount to the target user.
   - **uint256 extraPayTargetToOrigin**: A value greater than 0 indicates that target user has to pay an extra amount to the owner.

- **struct Swap**: This struct contains the swap data:
   - **House origin**: Stores the owner house.
   - **House target**: Stores the target house (The house from the offer accepted by the owner).
   - **uint256 amountPayOriginToTarget**: This value is copied from accepted offer.
   - **uint256 amountPayTargetToOrigin**: This value is also copied from accepted offer.

### The constructor

```solidity
constructor() {
    owner  = payable(msg.sender);
    status = Statuses.PENDING;
} 
```
The constructor is really simple. It sets the sender address (who deploys the contract) as the owner and sets the status as *PENDING*. Notice we convert sender address to payable so contract can transfer funds to it.

### Modifiers

```solidity
modifier hasToBeInitialized {
    require(keccak256(abi.encodePacked(status)) == keccak256(abi.encodePacked(Statuses.INITIALIZED)), 'An offer has been already accepted or contract has not been initialized');
    _;
}

modifier isOwner {
   require(msg.sender == owner, 'Required contract owner');
   _;
}
```

The contract creates two modifiers which will be used later:

- **modifier hasToBeInitialized**: Requires contract to be initialized, that is, variable *status* requires to be equal to *INITIALIZED* status.
- **modifier isOwner**: Requires sender to be the contract owner.

### Initialize contract

```solidity
function initialize(House memory house ) external isOwner {
    house.propietary = owner;
    swap.origin = house;
    status = Statuses.INITIALIZED;
}
```

The *initialize* function stores the house which owner wants to swap and marks the status as *INITIALIZED*. This function uses *isOwner* modifier so only contract owner can invoke it.

### Add Offers and confirm one

```solidity
function addOffer(House memory house, uint256 amountPayOriginToTarget, uint256 amountPayTargetToOrigin) external hasToBeInitialized {
   Offer memory offer = Offer(house, msg.sender, amountPayOriginToTarget, amountPayTargetToOrigin );
   emit NewOffer(offer);
}

function acceptOffer(address payable _targetUser, House memory house, uint256 amountPayOriginToTarget, uint256 amountPayTargetToOrigin) external hasToBeInitialized isOwner 
{
    targetUser = _targetUser;
    House memory targetHouse = house;

    swap.target = targetHouse;
    swap.amountPayOriginToTarget = amountPayOriginToTarget;
    swap.amountPayTargetToOrigin = amountPayTargetToOrigin;
    status = Statuses.ACCEPTED;
}
```

The *addOffer* function emits a *NewOffer* event with the offer data sent by a user interested on swapping. It uses the *Offer* struct to bundle offer data. This function requires the contract to be initialized (uses *hasToBeInitialized* modifier).

The *acceptOffer* function accepts an offer. It sets as *targetUser* the address received as first parameter. It also sets the accepted offers' data to the *swap* variable. Finally, it sets contract status as *ACCEPTED*.

### Deposit

```solidity
function deposit() external payable {
    if(swap.amountPayOriginToTarget > 0){
       require(msg.sender == owner,  'Origin must deposit enougth funds');
    }

    if(swap.amountPayTargetToOrigin > 0){
       require(msg.sender == targetUser,  'Target must deposit enougth funds');
    }

    balances[msg.sender] = msg.value;
    emit BalanceUpdated(msg.sender, msg.value);
}
```

The *deposit* function is a payable function. Both *owner* and *targetUser* can call it to deposit funds in the contract so that contract can transfer those funds later. As we saw at the begining of the article, the contract holds a mapping variable to store the balances. This function assigns the balance to the corresponding address and emits a *BalanceUpdated* event. Before doing this, it checks the following conditions:

- If *amountPayOriginToTarget* is greater that 0 then it requires *owner* to be the sender.
- If *amountPayTargetToOrigin* is greater that 0 then it requires *targetUser* to be the sender.

### Performs swap

```solidity
function performSwap() external
{
    require(targetUser == msg.sender, 'Only target user can confirm swap');
    require(keccak256(abi.encodePacked(status)) == keccak256(abi.encodePacked(Statuses.ACCEPTED)), 'An offer has not been accepted yet');

    if(swap.amountPayOriginToTarget > 0) {
        bool success = sendTransfer(owner, swap.amountPayOriginToTarget);
        require(success, "Transfer to target failed.");
    }

    if(swap.amountPayTargetToOrigin > 0) {
        bool success = sendTransfer(targetUser, swap.amountPayTargetToOrigin);
        require(success, "Transfer to owner failed.");
    }

    swap.origin.propietary = targetUser;
    swap.target.propietary = owner;
    status = Statuses.FINISHED;
}

function sendTransfer(address payable addr, uint256 amount ) private returns (bool){
    uint256 etherBalance = balances[addr] / 1 ether;
    require(etherBalance >= amount, 'Deposit has not been sent or is lower than required' );
    (bool success, ) = addr.call{value: amount}("");
    return success;
}
```

The *performSwap* function transfers funds from origin to target if *swap.amountPayOriginToTarget* is greater than 0 or from target to origin if *swap.amountPayTargetToOrigin* is greater than 0.
The private function *sendTransfer* receives the payable address to send funds to and the amount to send. As balances are stored on *wei*, function first converts it to ether and the compares with the amount to transfer. If there is no enougth balance, function will revert.
After sending transfer, *performSwap* sets *targetUser* as origin propietary and owner as target propietary. Finally, it sets the contract status as *FINISHED*.
As we can see at the first two lines, this function requires contract to hold an *ACCEPTED* status and to be *targetUser* who invokes it.

### Get Swap and status info

```solidity
function getStatus() public view returns (Statuses) {
    return status;
}

function info() public view hasToBeInitialized returns (Swap memory) {
    return swap;
}
```

The *getStatus* function is a view function (does not modify the contract status) which simply returns the current status.

The *info* function returns the swap data when the contract status is *INITIALIZED*. This allows users to query the swap info and make offers.

### Conclusion

This first part of the article has shown how to create a solidity contract for managing a house swap. In the next part we will learn how to compile and test our contract using the [hardhat](https://hardhat.org/) utilities.