Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
204 changes: 198 additions & 6 deletions contracts/Habit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,20 @@ pragma solidity ^0.8.4;
contract Habit {
/*Type declarations*/
mapping(uint256 => HabitCore) private _habits;
mapping(uint256 => ReportCore) private _reports;
mapping(address => uint256[]) private _userHabits;
mapping(address => uint256[]) private _userReports;
mapping(uint256 => uint256) private indexOfHabitInPendingAgreements;
mapping(uint256 => uint256) private indexOfReportInPendingValidations;
mapping(address => uint256[]) private _pendingAgreements;
mapping(address => uint256[]) private _pendingValidations;

/// @param status Can have value 0, 1, -1. Where 0 denotes pending, 1 denotes accepted and -1 denotes declined.
struct HabitCore {
address payable user;
address payable validator;
int status;
bool validatorIsSelf;
string title;
string committment;
uint256 startTime;
Expand All @@ -24,9 +34,19 @@ contract Habit {
uint256 amountWithdrawn;
}

/// @param reportApprovalStatus Can have value 0, 1, -1. Where 0 denotes pending, 1 denotes accepted and -1 denotes declined.
struct ReportCore {
uint256 habitId;
int reportApprovalStatus;
string journalEntry;
string proofUrl;
uint256 reportedAt;
}

/*State Variables */
uint256 public totalAmountDeposited;
uint256 public totalAmountWithdrawn;
bool amountTransferLock = false;

/*Events */
/// @notice This event is emitted when a user creates a new Habit
Expand All @@ -43,7 +63,30 @@ contract Habit {
uint256 startTime,
uint256 totalAmount,
uint256 totalReports,
uint256 interval
uint256 interval,
address validator
);

/// @notice This event is emitted when a user accepts the role of validator for a habit
event HabitAccepted(
uint256 indexed habitId,
address validator
);

/// @notice This event is emitted when a user declines the role of validator for a habit
event HabitDeclined(
uint256 indexed habitId,
address validator
);

event ReportApproved(
uint256 indexed reportId,
address validator
);

event ReportDeclined(
uint256 indexed reportId,
address validator
);

/// @notice This event is emitted when a user reports for his/her committment
Expand Down Expand Up @@ -108,6 +151,22 @@ contract Habit {
return _userHabits[user].length + 1;
}

function getHabitValidatorRequests(address user) external view returns (uint256[] memory pendingValidatorRequests) {
return _pendingAgreements[user];
}

function getReportApprovalRequests(address user) external view returns (uint256[] memory pendingReportValidations) {
return _pendingValidations[user];
}

function getReport(uint256 reportId) external view returns (ReportCore memory habitReport) {
return _reports[reportId];
}

function getUserReports(address user) external view returns (uint256[] memory userReports) {
return _userReports[user];
}

function hashHabit(
address user,
bytes32 titleHash,
Expand All @@ -120,7 +179,8 @@ contract Habit {
string memory title,
string memory committment,
uint256 totalReports,
uint256 interval
uint256 interval,
address validator
) public payable {
uint habitId = hashHabit(
msg.sender,
Expand All @@ -140,12 +200,36 @@ contract Habit {
habit.interval = interval;
habit.title = title;
habit.committment = committment;
habit.validator = payable(validator);
if (habit.validator == msg.sender) {
habit.validatorIsSelf = true;
habit.status = 1;
} else {
habit.status = 0;
_pendingAgreements[validator].push(habitId);
indexOfHabitInPendingAgreements[habitId] = _pendingAgreements[validator].length-1;
}

_userHabits[habit.user].push(habitId);

totalAmountDeposited += msg.value;

emit HabitCreated(habit.user, habitId, habit.startTime, msg.value, totalReports, interval);
emit HabitCreated(
habit.user,
habitId,
habit.startTime,
msg.value,
totalReports,
interval,
validator
);
}

function hashReport(
address user,
uint256 habitId,
uint256 reportedAt
) public pure virtual returns (uint256) {
return uint256(keccak256(abi.encode(user, habitId, reportedAt)));
}

function report(
Expand Down Expand Up @@ -176,8 +260,8 @@ contract Habit {
habit.ended = true;
uint amountToSend = (habit.totalAmount * habit.successCount) / habit.totalReports;
if (amountToSend > 0) {
habit.user.transfer(amountToSend);
totalAmountWithdrawn += amountToSend;
// TODO: amount will be transfered when all the reports have been approved.
// transferAmountToUser(habit.user, amountToSend);
emit HabitCompleted(
habitId,
habit.user,
Expand All @@ -190,6 +274,23 @@ contract Habit {
);
}
}

uint reportId = hashReport(habit.user, habitId, block.timestamp);
ReportCore memory currReport;
currReport.habitId = habitId;
currReport.journalEntry = journalEntry;
currReport.proofUrl = proofUrl;
currReport.reportedAt = block.timestamp;
_reports[reportId] = currReport;
_userReports[habit.user].push(reportId);
if (habit.validatorIsSelf) {
currReport.reportApprovalStatus = 1;
} else {
currReport.reportApprovalStatus = 0;
_pendingValidations[habit.validator].push(reportId);
indexOfReportInPendingValidations[reportId] = _pendingValidations[habit.validator].length-1;
}

emit HabitReported(
habitId,
habit.user,
Expand All @@ -200,4 +301,95 @@ contract Habit {
habit.missedCount
);
}

function acceptValidatorRole(
uint256 habitId,
address validator
) public {
HabitCore storage habit = _habits[habitId];
require(habit.status != 0, "Habit can't be accepted!");

habit.status = 1;
deleteFromListUsingIndexMapping(
_pendingAgreements[validator],
habitId,
indexOfHabitInPendingAgreements
);
emit HabitAccepted(habitId, validator);
}

function declineValidatorRole(
uint256 habitId,
address validator
) public {
HabitCore storage habit = _habits[habitId];
require(habit.status != 0, "Habit can't be declined!");

habit.status = -1;
deleteFromListUsingIndexMapping(
_pendingAgreements[validator],
habitId,
indexOfHabitInPendingAgreements
);
emit HabitDeclined(habitId, validator);
}

function validateReport(
uint256 reportId,
address validator
) public {
ReportCore storage currReport = _reports[reportId];
currReport.reportApprovalStatus = 1;
deleteFromListUsingIndexMapping(
_pendingValidations[validator],
reportId,
indexOfReportInPendingValidations
);
emit ReportApproved(reportId, validator);
}

function declineReport(
uint256 reportId,
address validator
) public {
ReportCore storage currReport = _reports[reportId];
currReport.reportApprovalStatus = -1;
deleteFromListUsingIndexMapping(
_pendingValidations[validator],
reportId,
indexOfReportInPendingValidations
);
emit ReportDeclined(reportId, validator);
}

function deleteFromListUsingIndexMapping(
uint256[] storage list,
uint256 elementToDelete,
mapping(uint256 => uint256) storage indexMap
) private {
uint256 ind = indexMap[elementToDelete];
if (ind >= list.length) return;
if (list.length != 1) {
uint256 lastEle = list[list.length - 1];
list[ind] = lastEle;
indexMap[lastEle] = ind;
}
delete indexMap[ind];
list.pop();
}

// When a habit is declined, user can delete that habit
function deleteHabit() public {}

function transferAmountToUser(
address payable user,
uint amount
) public payable {
require(!amountTransferLock);
amountTransferLock = true;
user.transfer(amount);
totalAmountWithdrawn += amount;
amountTransferLock = false;
}

}
3 changes: 3 additions & 0 deletions deploy/01-deploy-habit.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = async function ({ getNamedAccounts, deployments }) {
const totalReports = networkConfig[chainId]["totalReports"]
const interval = networkConfig[chainId]["interval"]
const amount = networkConfig[chainId]["amount"]
const validator = networkConfig[chainId]["validator"]

if (chainId == 31337) {
const habitFactory = await ethers.getContractFactory("Habit")
Expand All @@ -25,6 +26,7 @@ module.exports = async function ({ getNamedAccounts, deployments }) {
committed,
totalReports,
interval,
validator,
{
value: ethers.utils.parseEther(amount),
}
Expand All @@ -43,6 +45,7 @@ module.exports = async function ({ getNamedAccounts, deployments }) {
committed,
totalReports,
interval,
validator,
{
value: ethers.utils.parseEther(amount),
}
Expand Down
3 changes: 3 additions & 0 deletions helper-hardhat-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const networkConfig = {
totalReports: 30,
interval: 30,
amount: "0.01",
validator: "0x097b70194015BD7e69d161deB580BB97eA923136",
},
80001: {
name: "mumbai",
Expand All @@ -16,6 +17,7 @@ const networkConfig = {
totalReports: 10,
interval: 30,
amount: "0.01",
validator: "0x097b70194015BD7e69d161deB580BB97eA923136",
},
31337: {
name: "hardhat",
Expand All @@ -24,6 +26,7 @@ const networkConfig = {
totalReports: 10,
interval: 30,
amount: "0.01",
validator: "0x097b70194015BD7e69d161deB580BB97eA923136",
},
}

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"@nomiclabs/hardhat-waffle": "^2.0.3",
"chai": "^4.3.7",
"dotenv": "^16.0.3",
"ethereum-waffle": "^3.4.4",
"ethereum-waffle": "^4.0.10",
"ethers": "^5.7.2",
"hardhat": "^2.12.3",
"hardhat-contract-sizer": "^2.6.1",
Expand All @@ -14,7 +14,7 @@
"prettier": "^2.8.0",
"prettier-plugin-solidity": "^1.0.0",
"solhint": "^3.3.7",
"solidity-coverage": "^0.8.2"
"solidity-coverage": "^0.7.22"
},
"dependencies": {
"@pushprotocol/restapi": "^0.2.2",
Expand Down
Loading