# Contract

**Prerequisites**

* [NodeJS](https://nodejs.org/en)
* [Metamask](https://metamask.io/)

### Create Hardhat Project

Create a new folder named `coin_pusher` in the root directory to serve as the contract project folder. Then, navigate into this folder and install the required dependencies, `Hardhat` and `Sight Oracle`, by running the following commands:

```sh
cd coin_pusher
npm install --save-dev hardhat @sight-oracle/contracts dotenv
```

After successfully installing the dependencies, proceed to create a basic Hardhat project. In the previously created `coin_pusher` folder, run the following command:

```sh
npx hardhat init
```

Let’s choose create a `JavaScript` or `TypeScript` project. We recommend choosing TypeScript, but if you’re not familiar with it, simply select JavaScript.

<figure><img src="https://4239479252-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fc6n88IVs810uKFVbVyUQ%2Fuploads%2FpAY7d8pcCjfeXAozxdCZ%2F%E6%88%AA%E5%B1%8F2024-11-07%2017.07.31.png?alt=media&#x26;token=892d29d2-ffca-4f86-a596-e79d010f2f90" alt=""><figcaption></figcaption></figure>

### Writing Contract

Copy the following code into `/contracts/CoinPusher.sol`.

```solidity
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import "@sight-oracle/contracts/Oracle/Types.sol";
import "@sight-oracle/contracts/Oracle/Oracle.sol";
import "@sight-oracle/contracts/Oracle/ResponseResolver.sol";

enum State {
    Initial,
    Launching, // is setting target
    Launched, // target set done, user can play
    Completed, //
    Revealing,
    Revealed
}

// The FHE Coin Pusher Contract
contract CoinPusher is Ownable2Step {
    // Use Sight Oracle's RequestBuilder and ResponseResolver to interact with Sight Oracle
    using RequestBuilder for Request;
    using ResponseResolver for CapsulatedValue;

    event TargetSet(uint64 min, uint64 max);
    event Deposit(address indexed requester, uint64 indexed amount, uint64 sum);
    event DepositConfirmed(
        address indexed requester,
        uint64 indexed amount,
        uint64 sum
    );
    event TargetRevealed(uint64 target);
    event GameComplete(address indexed winner, uint64 sum);

    Oracle public oracle;
    State private _state;

    uint64 _min;
    uint64 _max;

    CapsulatedValue private _encrypted_target;
    uint64 private _plaintext_target; // not revealed until game ends, in micro-ether unit
    uint64 private _sum; // in micro-ether unit
    address private _winner;

    mapping(address => uint64) internal deposits; // in micro-ether unit
    mapping(address => bool) internal users; // can only deposit once
    mapping(bytes32 => bytes) requestExtraData;

    struct GameStatus {
        bool isComplete;
        address winner;
        uint64 target;
        State state;
        uint64 sum;
    }

    constructor(address oracle_, uint64 min, uint64 max) payable {
        oracle = Oracle(payable(oracle_));
        setTarget(min, max);
        _state = State.Launching;
        // initialize status
        _winner = address(0);
        _sum = 0;
    }

    function setTarget(uint64 min, uint64 max) private onlyOwner {
        require(_state != State.Launching, "Game is not complete!");
        require(max > min, "require max > min");
        // clear up
        _plaintext_target = 0;
        _min = min;
        _max = max;

        // Initialize new FHE computation request of 3 steps.
        Request memory r = RequestBuilder.newRequest(
            msg.sender,
            3,
            address(this),
            this.setTarget_cb.selector,
            ""
        );

        uint64 range = max - min + 1;
        uint64 shards = (type(uint64).max / range + 1);

        // Step 1: generate random value
        op encryptedValueA = r.rand();

        // Step 2 - 3: limit the random value into range min - max
        op scaled_random_value = r.div(encryptedValueA, shards);
        r.add(scaled_random_value, min);

        // Send the request via Sight FHE Oracle
        oracle.send(r);
    }

    // only Oracle can call this
    function setTarget_cb(
        bytes32 /* requestId */,
        CapsulatedValue[] memory EVs
    ) public onlyOracle {
        // Decode value from Oracle callback
        _encrypted_target = EVs[EVs.length - 1];

        _state = State.Launched;
        emit TargetSet(_min, _max);
    }

    // user make a deposit
    function deposit(uint8 flag) public payable {
        require(
            _state == State.Launched,
            "Game not launched or Game already Completed."
        );
        require(users[msg.sender] == false, "You have already deposited");

        uint64 amount = 0;
        uint256 value;

        if (flag == 0) {
            amount = 1000; // This could represent 1000 microether (0.001 ETH)
            value = 0.001 ether;
        } else if (flag == 1) {
            amount = 5000; // This could represent 5000 microether (0.005 ETH)
            value = 0.005 ether;
        } else if (flag == 2) {
            amount = 10000; // This could represent 10000 microether (0.01 ETH)
            value = 0.01 ether;
        } else {
            revert("only flag 1 - small, 2 - medium, 3 - large allowed");
        }

        // Check if the sent value matches the required amount
        require(msg.value == value, "Incorrect payment amount.");

        users[msg.sender] = true;
        deposits[msg.sender] = amount;
        _sum = _sum + amount;

        // Initialize new FHE computation request of 3 steps.
        Request memory r = RequestBuilder.newRequest(
            msg.sender,
            3,
            address(this),
            this.deposit_cb.selector,
            ""
        );

        // Step 1: load local stored encrypted target into request processing context
        op e_target = r.getEuint64(_encrypted_target.asEuint64());

        // Step 2: compare balance and encrypted_target
        op e_greater = r.ge(_sum, e_target);

        // Step 3: decrypt the comparison result, it is safe to reveal
        r.decryptEbool(e_greater);

        // requestExtraData[r.id] = abi.encode(msg.sender, amount, _sum);

        // send request to Sight FHE Oracle
        oracle.send(r);

        emit Deposit(msg.sender, amount, _sum);
    }

    // only Oracle can call this
    function deposit_cb(
        bytes32 requestId,
        CapsulatedValue[] memory EVs
    ) public onlyOracle {
        if (_state != State.Launched) {
            return;
        }

        bytes memory extraData = requestExtraData[requestId];
        (address requester, uint64 amount, uint64 sum) = abi.decode(
            extraData,
            (address, uint64, uint64)
        );

        emit DepositConfirmed(requester, amount, sum);

        // Check winning condition
        // CapsulatedValue 0: the encrypted target
        // CapsulatedValue 1: the encrypted compare result
        // CapsulatedValue 2: the decrypted compare result, as used here
        bool isWinner = EVs[EVs.length - 1].asBool();
        if (isWinner) {
            _winner = requester;
            _state = State.Completed;
            emit GameComplete(_winner, _sum);
            revealTarget();
        }
    }

    // Reveal the target
    function revealTarget() private {
        require(_state == State.Completed, "Game is not complete!");
        _state = State.Revealing;
        // Initialize new FHE computation request of 2 steps.
        Request memory r = RequestBuilder.newRequest(
            msg.sender,
            2,
            address(this),
            this.revealTarget_cb.selector,
            ""
        );

        // Step 1: load encrypted target into processing context
        op e_target = r.getEuint64(_encrypted_target.asEuint64());

        // Step 2: decrypt the target
        r.decryptEuint64(e_target);

        oracle.send(r);
    }

    // only Oracle can call this
    function revealTarget_cb(
        bytes32 /* requestId */,
        CapsulatedValue[] memory EVs
    ) public onlyOracle {
        CapsulatedValue memory wrapped_plaintext_target = EVs[EVs.length - 1];

        // unwrap the plaintext value
        _plaintext_target = wrapped_plaintext_target.asUint64();
        _state = State.Revealed;
        emit TargetRevealed(_plaintext_target);
    }

    modifier onlyOracle() {
        require(msg.sender == address(oracle), "Only Oracle Can Do This");
        _;
    }

    function depositOf(address addr) public view returns (uint64) {
        return deposits[addr];
    }

    function getGameStatus() public view returns (GameStatus memory) {
        return
            GameStatus({
                isComplete: _state == State.Completed,
                winner: _winner,
                target: _plaintext_target,
                state: _state,
                sum: _sum
            });
    }

    function withdraw() external onlyOwner {
        require(
            _state == State.Completed ||
                _state == State.Revealing ||
                _state == State.Revealed,
            "Invalid state"
        );
        require(address(this).balance > 0, "No funds to withdraw");
        payable(owner()).transfer(address(this).balance);
    }

    fallback() external payable {}
    receive() external payable {}
}
```

This is the core contract code for the entire game, ensuring that the game is fair, secure, and transparent. It may seem complex, but don’t worry—we’ll break it down and provide a clear, detailed explanation of each section.

```solidity
enum State {
    Initial,
    Launching, // is setting target
    Launched, // target set done, user can play
    Completed, //
    Revealing,
    Revealed
}
```

First, we defined an `enum` type called `State`, which will be used to track the game’s status.

```solidity
contract CoinPusher is Ownable2Step {
    // Use Sight Oracle's RequestBuilder and ResponseResolver to interact with Sight Oracle
    using RequestBuilder for Request;
    using ResponseResolver for CapsulatedValue;
```

[`Ownable2Step`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol) is a contract for ownership control that inherits from `Ownable` and offers enhanced security compared to `Ownable`.

The contract uses the `RequestBuilder` library to construct requests and the `ResponseResolver` library to interpret the responses.

```solidity
event TargetSet(uint64 min, uint64 max);
event Deposit(address indexed requester, uint64 indexed amount, uint64 sum);
event DepositConfirmed(
    address indexed requester,
    uint64 indexed amount,
    uint64 sum
);
event TargetRevealed(uint64 target);
event GameComplete(address indexed winner, uint64 sum);
```

Here are some `Event` definitions. An `Event` is a mechanism for recording changes in the contract’s state, allowing external applications to listen to and query these changes. You can use `emit` to trigger an `Event`.

```solidity
Oracle public oracle;
State private _state;

uint64 _min;
uint64 _max;

CapsulatedValue private _encrypted_target;
uint64 private _plaintext_target; // not revealed until game ends, in micro-ether unit
uint64 private _sum; // in micro-ether unit
address private _winner;

mapping(address => uint64) internal deposits; // in micro-ether unit
mapping(address => bool) internal users; // can only deposit once
mapping(bytes32 => bytes) requestExtraData;

struct GameStatus {
    bool isComplete;
    address winner;
    uint64 target;
    State state;
    uint64 sum;
}
```

**This code defines several state variables:**&#x20;

* `oracle` is an instance of `Oracle` used to handle cryptographic operations and send requests. &#x20;
* `_state` tracks the current state of the game.
* `_min` and `_max` represent the minimum and maximum range of the game’s target value.
* `_encrypted_target` and `_plaintext_target` store the encrypted and decrypted target values.
* `_sum` records the current total deposit amount.
* `_winner` holds the winner’s address.
* `deposits` keeps track of each player’s deposit amount.
* `users` indicates whether a user has participated in the game.
* `requestExtraData` contains additional data for requests sent to the Oracle.
* `GameStatus` is a data structure that simplifies checking the status of various game variables.

**Constructor Function**

```solidity
constructor(address oracle_, uint64 min, uint64 max) payable {
    oracle = Oracle(payable(oracle_));
    setTarget(min, max);
    _state = State.Launching;
    // initialize status
    _winner = address(0);
    _sum = 0;
}
```

The `constructor` function takes three parameters, which are used to initialize the basic settings and state of the game. These parameters must be provided when deploying the contract.

**SetTarget Function**

```solidity
function setTarget(uint64 min, uint64 max) private onlyOwner {
    require(_state != State.Launching, "Game is not complete!");
    require(max > min, "require max > min");
    // clear up
    _plaintext_target = 0;
    _min = min;
    _max = max;

    // Initialize new FHE computation request of 3 steps.
    Request memory r = RequestBuilder.newRequest(
        msg.sender,
        3,
        address(this),
        this.setTarget_cb.selector,
        ""
    );

    uint64 range = max - min + 1;
    uint64 shards = (type(uint64).max / range + 1);

    // Step 1: generate random value
    op encryptedValueA = r.rand();

    // Step 2 - 3: limit the random value into range min - max
    op scaled_random_value = r.div(encryptedValueA, shards);
    r.add(scaled_random_value, min);

    // Send the request via Sight FHE Oracle
    oracle.send(r);
}
```

The `setTarget` function is called once within the constructor to initialize the target range.

The `onlyOwner` modifier from the `Ownable2Step` contract, restricts access to the `setTarget` function, allowing only the contract owner to call it. A new request is created using `RequestBuilder.newRequest`, which specifies the sender, the number of computation steps, the callback address, and the callback function. The `rand` function is then called to generate a random encrypted value, which is stored in the Sight Network. To ensure the generated random value falls within the specified min and max range, the encrypted `div` and `add` functions are used for secure division and addition operations. Finally, the `send` function submits the request to the Sight Oracle.

**setTarget\_cb function**

```solidity
function setTarget_cb(
        bytes32 /* requestId */,
        CapsulatedValue[] memory EVs
    ) public onlyOracle {
        // Decode value from Oracle callback
        _encrypted_target = EVs[EVs.length - 1];

        _state = State.Launched;
        emit TargetSet(_min, _max);
    }
```

`setTarget_cb` is a callback function (with “cb” standing for callback) that has an `onlyOracle` modifier, meaning it can only be called by the `Oracle` contract. `EVs[EVs.length - 1]` will retrieve the last element from the `EVs` array and assign it to `_encrypted_target`, which means storing the encrypted value returned from the Oracle in `_encrypted_target`. Then, the `_state` is updated to `Launched`, indicating that the game has started. Finally, the `TargetSet` event is triggered to notify listeners that the target range has been set.

**deposit function**

```solidity
function deposit(uint8 flag) public payable {
        require(
            _state == State.Launched,
            "Game not launched or Game already Completed."
        );
        require(users[msg.sender] == false, "You have already deposited");

        uint64 amount = 0;
        uint256 value;

        if (flag == 0) {
            amount = 1000; // This could represent 1000 microether (0.001 ETH)
            value = 0.001 ether;
        } else if (flag == 1) {
            amount = 5000; // This could represent 5000 microether (0.005 ETH)
            value = 0.005 ether;
        } else if (flag == 2) {
            amount = 10000; // This could represent 10000 microether (0.01 ETH)
            value = 0.01 ether;
        } else {
            revert("only flag 1 - small, 2 - medium, 3 - large allowed");
        }

        // Check if the sent value matches the required amount
        require(msg.value == value, "Incorrect payment amount.");

        users[msg.sender] = true;
        deposits[msg.sender] = amount;
        _sum = _sum + amount;

        // Initialize new FHE computation request of 3 steps.
        Request memory r = RequestBuilder.newRequest(
            msg.sender,
            3,
            address(this),
            this.deposit_cb.selector,
            ""
        );

        // Step 1: load local stored encrypted target into request processing context
        op e_target = r.getEuint64(_encrypted_target.asEuint64());

        // Step 2: compare balance and encrypted_target
        op e_greater = r.ge(_sum, e_target);

        // Step 3: decrypt the comparison result, it is safe to reveal
        r.decryptEbool(e_greater);

        // send request to Sight FHE Oracle
        bytes32 lastReqId = oracle.send(r);

        requestExtraData[lastReqId] = abi.encode(msg.sender, amount, _sum);

        emit Deposit(msg.sender, amount, _sum);
    }
```

The `deposit` function is used to handle user deposits behavior. When calling `deposit`, you need to pass in a parameter of 0, 1, or 2, and send the corresponding amount of Ether. These parameters correspond to three different deposit amounts. Once the player's deposit is confirmed successful, the user is marked as having deposited, and the user's deposit amount and the total deposit amount `_sum` are updated. Then, a request is initialized through `RequestBuilder.newRequest`. The `getEuint64` function will load the locally stored encrypted target value into the request's processing context, and the `ge` function will compare `_sum` with `e_target`. `ge` stands for greater than or equal to. If `_sum` is greater than or equal to `e_target`, the result will be true; otherwise, it will be false. `decryptEbool` is used to decrypt the encrypted boolean value. Finally, record some information about the request in `requestExtraData`.

**deposit\_cb function**

```solidity
function deposit_cb(
        bytes32 requestId,
        CapsulatedValue[] memory EVs
    ) public onlyOracle {
        if (_state != State.Launched) {
            return;
        }

        bytes memory extraData = requestExtraData[requestId];
        (address requester, uint64 amount, uint64 sum) = abi.decode(
            extraData,
            (address, uint64, uint64)
        );

        emit DepositConfirmed(requester, amount, sum);

        // Check winning condition
        // CapsulatedValue 0: the encrypted target
        // CapsulatedValue 1: the encrypted compare result
        // CapsulatedValue 2: the decrypted compare result, as used here
        bool isWinner = EVs[EVs.length - 1].asBool();
        if (isWinner) {
            _winner = requester;
            _state = State.Completed;
            emit GameComplete(_winner, _sum);
            revealTarget();
        }
    }
```

First, retrieve the `extraData` corresponding to the requestId from `requestExtraData`. Then, decode the `extraData` to obtain information about the deposit request, and trigger the `DepositConfirmed` event to indicate that the deposit transaction has been successfully completed. The line of code `EVs[EVs.length - 1].asBool()` retrieves the last CapsulatedValue from the `EVs` array and assigns its boolean value to `isWinner`. If `isWinner` is true, it means the sender of this request has won the competition. Therefore, update `_winner` to `requester` and change the game's status to `Completed`. Finally, call `revealTarget` to convert the encrypted target value into plaintext.

**revealTarget function**

```solidity
function revealTarget() private {
        require(_state == State.Completed, "Game is not complete!");
        _state = State.Revealing;
        // Initialize new FHE computation request of 2 steps.
        Request memory r = RequestBuilder.newRequest(
            msg.sender,
            2,
            address(this),
            this.revealTarget_cb.selector,
            ""
        );

        // Step 1: load encrypted target into processing context
        op e_target = r.getEuint64(_encrypted_target.asEuint64());

        // Step 2: decrypt the target
        r.decryptEuint64(e_target);

        oracle.send(r);
    }
```

The `revealTarget` function loads the locally stored encrypted target value `_encrypted_target` into the request's processing context by using `getEuint64` and assigns it to `e_target`. It then decrypts `e_target` by performing a decryption operation with `decryptEuint64`.

**revealTarget\_cb function**

```solidity
function revealTarget_cb(
        bytes32 /* requestId */,
        CapsulatedValue[] memory EVs
    ) public onlyOracle {
        CapsulatedValue memory wrapped_plaintext_target = EVs[EVs.length - 1];

        // unwrap the plaintext value
        _plaintext_target = wrapped_plaintext_target.asUint64();
        _state = State.Revealed;
        emit TargetRevealed(_plaintext_target);
    }
```

In the `revealTarget_cb` function, the plaintext of the `target` returned by the Oracle will be stored in `_plaintext_target`, then the game status will be updated to `Revealed` and the `TargetRevealed` event will be emitted.

The above describes the core functionalities of the Coin Pusher game. There are also some functions for reading contract states, but I won't go into detail about those. Next, let's proceed to deploy the contract to the Sepolia test network.

### Deploy Contract

To deploy the contract on the Sepolia test network, you first need to obtain some Sepolia test tokens and the Sepolia RPC URL.

If you don’t have a **Sepolia RPC** yet, you can click [here](https://dashboard.alchemy.com/) to require one.

> **Note:** You can get the `private key` through **MetaMask** by going to Account Details => Export Private Key (Please use a test wallet, as leaking the private key poses a risk of account theft!).

```properties
PRIVATE_KEY=${YOUR_PRIVATE_KEY}
SEPOLIA_RPC_URL=${YOUR_SEPOLIA_RPC_URL}
SEPOLIA_ORACLE_CONTRACT_ADDRESS=0xC5ac65f17Ce781E9F325634b6218Dc75a5CF9abF
```

Add these environment variables to the `.env` file. `SEPOLIA_ORACLE_CONTRACT_ADDRESS` is the Oracle address deployed on the Sepolia test network.

Copy the following deployment code into `/ignition/modules/CoinPusher.ts`.

```typescript
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";

const oracleAddr = process.env.SEPOLIA_ORACLE_ADDRESS!;

const Coin_PusherModule = buildModule("Coin_PusherModule", (m) => {
  const Coin_Pusher = m.contract("CoinPusher", [oracleAddr, 10000, 50000]);
  return { Coin_Pusher };
});

export default Coin_PusherModule;
```

**Explanation:**

* **buildModule**: The first aspect to note is that modules are created by calling the `buildModule` function, which requires a module ID and a callback function. Our module will be identified as `"Coin_PusherModule"`.
* **m**: The `m` parameter being passed into the callback is an instance of a `ModuleBuilder`, which is an object with methods to define and configure your smart contract instances.
* **oracleAddr:** `oracleAddr` is the address of a Sight Oracle, used for requesting and responding to data. The address is chosen based on the network.
* **m.contract("CoinPusher", \[oracleAddr, 10000, 50000])**: Deploys the CoinPusher contract using Ignition and passes the Oracle address as a constructor parameter. 10000 and 50000 are the minimum and maximum values for initializing the target.

**Running the Deployment Script**

```sh
npx hardhat ignition deploy ignition/modules/Example.ts --network sepolia
```

After a successful deployment, you will see some deployment information printed to the console, including the Example contract address.

<figure><img src="https://4239479252-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fc6n88IVs810uKFVbVyUQ%2Fuploads%2FOXybNCzIdfRoD2whluCI%2F%E6%88%AA%E5%B1%8F2024-11-08%2013.56.16.png?alt=media&#x26;token=c6927e8a-7a10-4a20-84e1-88ea3f7a96b5" alt=""><figcaption></figcaption></figure>

After deploying the contract, you will find a new file named `deployments/chain-11155111` under the `ignition` directory. This file contains the contract's ABI and contract address, which we will need to use when building the frontend to interact with the contract.

<figure><img src="https://4239479252-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fc6n88IVs810uKFVbVyUQ%2Fuploads%2FViMizEJZjCRvGHy6Xp8n%2F%E6%88%AA%E5%B1%8F2024-11-08%2014.13.11.png?alt=media&#x26;token=b8c42ee4-ef2e-464c-bf99-7c34a9d993a8" alt=""><figcaption></figcaption></figure>

Congratulations on completing the smart contract part of the dapp! Take a break, and then click to join us in building the frontend pages of the dapp.
