Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/x2earn rewards pool #19

Merged
merged 23 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
070f2b3
feat: set x2EarnRewardsPool contract and appId in EcoEarn
Agilulfo1820 Jun 22, 2024
c973386
feat: distribute rewards through dao contract
Agilulfo1820 Jun 22, 2024
5f5ab07
feat: use x2EarnContract to distribute token
Agilulfo1820 Jun 22, 2024
e2cd036
revert: claimAllocation
Agilulfo1820 Jun 22, 2024
441c40c
feat: admin does not need to claim, but only set amount for next cycle
Agilulfo1820 Jun 22, 2024
619ffa1
feat: remove IToken from EcoEarn contract (not needed anymore)
Agilulfo1820 Jun 22, 2024
37c4a32
Merge branch 'main' into feat/x2earn-rewards-pool
Agilulfo1820 Jun 22, 2024
3fe609b
Merge remote-tracking branch 'origin/main' into feat/x2earn-rewards-pool
Agilulfo1820 Jul 15, 2024
8ada0f5
fix: compile contract
Agilulfo1820 Jul 17, 2024
2f26f3e
feat: added mock contracts
Agilulfo1820 Jul 23, 2024
ea3002c
feat: added new deploy script
Agilulfo1820 Jul 23, 2024
5b8b140
feat: updated test deploy helper
Agilulfo1820 Jul 23, 2024
e3e82b8
chore: add docs in readme
Agilulfo1820 Jul 23, 2024
5ce629a
fix: tests
Agilulfo1820 Jul 23, 2024
7e1fec3
fix: yarn lint
Agilulfo1820 Jul 23, 2024
af3f055
fix: warn devs to use the test sandbox
Agilulfo1820 Jul 23, 2024
fd9e7ad
chore: added comment regarding sustainability proof
Agilulfo1820 Jul 23, 2024
0bfad30
feat: updated readme
Agilulfo1820 Jul 23, 2024
aec30a8
chore: added testnet env link
Agilulfo1820 Jul 23, 2024
cfaae32
chore: updated readme
Agilulfo1820 Jul 23, 2024
5b084b2
Merge remote-tracking branch 'origin/main' into feat/x2earn-rewards-pool
Agilulfo1820 Jul 23, 2024
75e857c
fix: typo
Agilulfo1820 Jul 23, 2024
75fa6ed
feat: added more steps in readme
Agilulfo1820 Jul 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

Unlock the potential of decentralized application development on Vechain with our X-App template for VeBetterDAO. Designed for the Vechain Thor blockchain, this template integrates cutting-edge technologies such as React, TypeScript, Hardhat, and Express, ensuring a seamless and efficient DApp development experience. 🌟

Read more about the implementation and key features of this template in our [Developer Docs](https://docs.vebetterdao.org/developer-guides/integration-examples/pattern-2-use-smart-contracts-and-backend).

## Requirements

Ensure your development environment is set up with the following:
Expand All @@ -33,14 +35,17 @@ Ensure your development environment is set up with the following:
- **Hardhat (for smart contracts):** [Getting Started with Hardhat](https://hardhat.org/hardhat-runner/docs/getting-started) ⛑️

## Project Structure

### Frontend (apps/frontend) 🌐

A blazing-fast React application powered by Vite:

- **Vechain dapp-kit:** Streamline wallet connections and interactions. [Learn more](https://docs.vechain.org/developer-resources/sdks-and-providers/dapp-kit)

### Backend (apps/backend) 🔙

An Express server crafted with TypeScript for robust API development:

- **Vechain SDK:** Seamlessly fetch and perform transactions with the VechainThor blockchain. [Learn more](https://docs.vechain.org/developer-resources/sdks-and-providers/sdk)
- **OpenAI GPT-Vision-Preview:** Integrate image analysis capabilities. [Explore here](https://platform.openai.com/docs/guides/vision)

Expand All @@ -59,29 +64,35 @@ Configure your environment variables for seamless integration:
### Frontend

Place your `.env` files in `apps/frontend`:

- **VITE_RECAPTCHA_V3_SITE_KEY:** [Request your RecaptchaV3 site keys](https://developers.google.com/recaptcha/docs/v3)

### Backend

Store your environment-specific `.env` files in `apps/backend`. `.env.development.local` & `.env.production.local` allow for custom environment variables based on the environment:

- **OPENAI_API_KEY:** [Get your GPT-4 OpenAI key](https://platform.openai.com/api-keys) (Enable GPT-4 [here](https://help.openai.com/en/articles/7102672-how-can-i-access-gpt-4-gpt-4-turbo-and-gpt-4o))
- **RECAPTCHA_SECRET_KEY:** [Request your RecaptchaV3 site keys](https://developers.google.com/recaptcha/docs/v3)

### Contracts

Manage deployment parameters and network configurations in `hardhat.config.js` under `apps/contracts`:

- **MNEMONIC:** Mnemonic of the deploying wallet

## Getting Started 🏁

Clone the repository and install dependencies with ease:

```bash
yarn install # Run this at the root level of the project
```

## Deploying Contracts 🚀

Deploy your contracts effortlessly on either the Testnet or Solo networks:
Deploy your contracts effortlessly on either the Testnet or Solo networks.
If you are deploying on the Testnet, ensure you have the correct addresses in the `config-contracts` package.
When deploying on the SOLO network the script will deploy for you mocked VeBetterDAO contracts.

### Deploying on Solo Network

Expand All @@ -98,25 +109,29 @@ yarn contracts:deploy:testnet
## Running on Solo Network 🔧

Run the Frontend and Backend, connected to the Solo network and pointing to your deployed contracts. Ensure all environment variables are properly configured:

```bash
yarn dev
yarn dev
```

### Setting up rewards
Run vechain devpal

Run vechain devpal

```bash
npx @vechain/devpal http://localhost:8669
```

Open the `Inspector` tab and perform the following transactions:

- **Add Contracts:** Add the EcoEarn contract and the Token contract deployed previously. Addresses can be found in the `config-contracts` package. ABIs can be found in the artifacts folder of the `contracts` app.
![image](https://github.com/vechain/x-app-template/assets/64158778/e288ada4-5973-4428-9e72-a362388b1826)
![image](https://github.com/vechain/x-app-template/assets/64158778/e288ada4-5973-4428-9e72-a362388b1826)
- **Approve token:** Approve the EcoEarn contract to spend your tokens
![image](https://github.com/vechain/x-app-template/assets/64158778/70787d8d-ae60-40ea-b277-87359aaca4ee)
![image](https://github.com/vechain/x-app-template/assets/64158778/70787d8d-ae60-40ea-b277-87359aaca4ee)
- **Claim rewards:** Claim rewards for the EcoEarn contract
![image](https://github.com/vechain/x-app-template/assets/64158778/834437e5-8de1-4802-9ed7-dca6fe4df332)
![image](https://github.com/vechain/x-app-template/assets/64158778/834437e5-8de1-4802-9ed7-dca6fe4df332)
- **Trigger cycle:** Trigger the cycle for the EcoEarn contract
![image](https://github.com/vechain/x-app-template/assets/64158778/00236dcd-5b64-4493-9acd-55c6a7f0981f)
![image](https://github.com/vechain/x-app-template/assets/64158778/00236dcd-5b64-4493-9acd-55c6a7f0981f)

## Disclaimer ⚠️

Expand Down
56 changes: 36 additions & 20 deletions apps/contracts/contracts/EcoEarn.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,24 @@
pragma solidity ^0.8.19;

import '@openzeppelin/contracts/access/AccessControl.sol';
import './interfaces/IToken.sol';
import './interfaces/IX2EarnRewardsPool.sol';
import '@openzeppelin/contracts/utils/Strings.sol';

/**
* @title EcoEarn Contract
* @dev This contract manages a reward system based on cycles. Participants can make valid submissions to earn rewards.
* Rewards are being distributed by interacting with the VeBetterDAO's X2EarnRewardsPool contract.
*
* @notice To distribute rewards this contract necesitates of a valid APP_ID provided by VeBetterDAO. This contract
* can be initially deployed without this information and DEFAULT_ADMIN_ROLE can update it later through {EcoEarn-setAppId}.
* This contract must me set as a `rewardDistributor` inside the X2EarnApps contract to be able to send rewards to users and withdraw.
Agilulfo1820 marked this conversation as resolved.
Show resolved Hide resolved
*/
contract EcoEarn is AccessControl {
IToken public token;
// The X2EarnRewardsPool contract used to distribute rewards
IX2EarnRewardsPool public x2EarnRewardsPoolContract;

// AppID given by the X2EarnApps contract of VeBetterDAO
bytes32 public appId;

// Mapping from cycle to total rewards
mapping(uint256 => uint256) public rewards;
Expand Down Expand Up @@ -67,15 +77,20 @@ contract EcoEarn is AccessControl {
/**
* @dev Constructor for the EcoEarn contract
* @param _admin Address of the admin
* @param _token Address of the token contract
* @param _x2EarnRewardsPoolContract Address of the X2EarnRewardsPool contract
* @param _cycleDuration Duration of each cycle in blocks
* @param _maxSubmissionsPerCycle Maximum submissions allowed per cycle
* @param _appId The appId generated by the X2EarnApps contract when app was added to VeBetterDAO
*/
constructor(address _admin, address _token, uint256 _cycleDuration, uint256 _maxSubmissionsPerCycle) {
token = IToken(_token);
constructor(address _admin, address _x2EarnRewardsPoolContract, uint256 _cycleDuration, uint256 _maxSubmissionsPerCycle, bytes32 _appId) {
require(_admin != address(0), 'EcoEarn: _admin address cannot be the zero address');
require(_x2EarnRewardsPoolContract != address(0), 'EcoEearn: x2EarnRewardsPool contract address cannot be the zero address');

x2EarnRewardsPoolContract = IX2EarnRewardsPool(_x2EarnRewardsPoolContract);
maxSubmissionsPerCycle = _maxSubmissionsPerCycle;
cycleDuration = _cycleDuration;
nextCycle = 1;
appId = _appId;
_grantRole(DEFAULT_ADMIN_ROLE, _admin);
}

Expand Down Expand Up @@ -106,21 +121,20 @@ contract EcoEarn is AccessControl {
// Decrease the rewards left
rewardsLeft[getCurrentCycle()] -= amount;

// Transfer the reward to the participant
require(token.transfer(participant, amount));
// Transfer the reward to the participant, will revert if the transfer fails
x2EarnRewardsPoolContract.distributeReward(appId, amount, participant, '');
Agilulfo1820 marked this conversation as resolved.
Show resolved Hide resolved

emit Submission(participant, amount);
}

/**
* @dev Claims allocation for the next cycle
* @dev Set the allocation for the next cycle
* @param amount Amount of tokens to be allocated
*/
function claimAllocation(uint256 amount) public onlyRole(DEFAULT_ADMIN_ROLE) {
require(amount <= token.balanceOf(msg.sender), 'EcoEarn: Insufficient balance');
function setRewardsAmount(uint256 amount) public onlyRole(DEFAULT_ADMIN_ROLE) {
require(amount <= x2EarnRewardsPoolContract.availableFunds(appId), 'EcoEarn: Insufficient balance on the X2EarnRewardsPool contract');
rewards[nextCycle] = amount;
rewardsLeft[nextCycle] = amount;
require(token.transferFrom(msg.sender, address(this), amount));
emit ClaimedAllocation(nextCycle, amount);
}

Expand All @@ -133,7 +147,9 @@ contract EcoEarn is AccessControl {
require(cycle < getCurrentCycle(), 'EcoEarn: Cycle is not over');
uint256 amount = rewardsLeft[cycle];
rewardsLeft[cycle] = 0;
require(token.transfer(msg.sender, amount));

// will revert if the withdraw fails
x2EarnRewardsPoolContract.withdraw(amount, appId, string.concat('Withdraws remaining rewards of cycle nr.', Strings.toString(cycle)));
}

// ---------------- SETTERS ---------------- //
Expand All @@ -147,14 +163,6 @@ contract EcoEarn is AccessControl {
maxSubmissionsPerCycle = _maxSubmissionsPerCycle;
}

/**
* @dev Sets the token address
* @param _token New token contract address
*/
function setToken(address _token) external onlyRole(DEFAULT_ADMIN_ROLE) {
token = IToken(_token);
}

/**
* @dev Sets the next cycle number
* @param _nextCycle New next cycle number
Expand All @@ -163,6 +171,14 @@ contract EcoEarn is AccessControl {
nextCycle = _nextCycle;
}

/**
* @dev Sets the appId provied by VeBetterDAO
* @param _appId The new app id
*/
function setAppId(bytes32 _appId) external onlyRole(DEFAULT_ADMIN_ROLE) {
appId = _appId;
}

// ---------------- GETTERS ---------------- //

/**
Expand Down
14 changes: 0 additions & 14 deletions apps/contracts/contracts/Token.sol

This file was deleted.

83 changes: 83 additions & 0 deletions apps/contracts/contracts/interfaces/IX2EarnRewardsPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.20;

/**
* @title IX2EarnRewardsPool
* @dev Interface designed to be used by a contract that allows x2Earn apps to reward users that performed sustainable actions.
* Funds can be deposited into this contract by specifying the app id that can access the funds.
* Admins of x2EarnApps can withdraw funds from the rewards pool, whihc are sent to the team wallet.
*/
interface IX2EarnRewardsPool {
/**
* @dev Event emitted when a new deposit is made into the rewards pool.
*
* @param amount The amount of $B3TR deposited.
* @param appId The ID of the app for which the deposit was made.
* @param depositor The address of the user that deposited the funds.
*/
event NewDeposit(uint256 amount, bytes32 indexed appId, address indexed depositor);

/**
* @dev Event emitted when a team withdraws funds from the rewards pool.
*
* @param amount The amount of $B3TR withdrawn.
* @param appId The ID of the app for which the withdrawal was made.
* @param teamWallet The address of the team wallet that received the funds.
* @param withdrawer The address of the user that withdrew the funds.
* @param reason The reason for the withdrawal.
*/
event TeamWithdrawal(uint256 amount, bytes32 indexed appId, address indexed teamWallet, address withdrawer, string reason);

/**
* @dev Event emitted when a reward is emitted by an app.
*
* @param amount The amount of $B3TR rewarded.
* @param appId The ID of the app that emitted the reward.
* @param receiver The address of the user that received the reward.
* @param proof The proof of the sustainable action that was performed.
* @param distributor The address of the user that distributed the reward.
*/
event RewardDistributed(uint256 amount, bytes32 indexed appId, address indexed receiver, string proof, address indexed distributor);

/**
* @dev Retrieves the current version of the contract.
*
* @return The version of the contract.
*/
function version() external pure returns (string memory);

/**
* @dev Function used by x2earn apps to deposit funds into the rewards pool.
*
* @param amount The amount of $B3TR to deposit.
* @param appId The ID of the app.
*/
function deposit(uint256 amount, bytes32 appId) external returns (bool);

/**
* @dev Function used by x2earn apps to withdraw funds from the rewards pool.
*
* @param amount The amount of $B3TR to withdraw.
* @param appId The ID of the app.
* @param reason The reason for the withdrawal.
*/
function withdraw(uint256 amount, bytes32 appId, string memory reason) external;

/**
* @dev Gets the amount of funds available for an app to reward users.
*
* @param appId The ID of the app.
*/
function availableFunds(bytes32 appId) external view returns (uint256);

/**
* @dev Function used by x2earn apps to reward users that performed sustainable actions.
*
* @param appId the app id that is emitting the reward
* @param amount the amount of B3TR token the user is rewarded with
* @param receiver the address of the user that performed the sustainable action and is rewarded
* @param proof a JSON file uploaded on IPFS by the app that adds information on the type of action that was performed
*/
function distributeReward(bytes32 appId, uint256 amount, address receiver, string memory proof) external;
}
20 changes: 20 additions & 0 deletions apps/contracts/contracts/mock/B3TR_Mock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

/**
* @title B3TR_Mock
* @dev Mock contract for the B3TR token.
*/
contract B3TR_Mock is ERC20 {
// Mint 10,000,000 B3TR tokens to the deployer
constructor() ERC20("B3TR", "B3TR") {
_mint(msg.sender, 10000000 * 10 ** decimals());
}

// Public function to mint tokens
function mint(address to, uint256 amount) public {
_mint(to, amount);
}
}
Loading
Loading