Skip to content
Merged
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
19 changes: 2 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,13 @@ Currently Ganache and Node 14 are not playing well together. To get started:

## Features

Single Player mode features:

- [x] Creation of Commitment
- [ ] Management of Activities
- [ ] Management of Measures
- [x] Execution of commitment settlement

#### Creation of Commitment

A commitment consists of an ```activity```, a ```measure``` for given activity, a ```start time```, and ```stake```. We will set the ```end date``` 7 days after the startdate.
A commitment consists of an ```activity```, a ```goalValue``` for given activity, a ```startTime```, and ```stake```. We will automagically set the ```endTime``` 7 days after the startdate.

#### Management of Activities

An activity consists of a ```name```, a ```measure``` to express activity metrics, an array of accepted ```ranges```, and the ```oracle``` address. Activities can be enabled by setting it to ```allowed```.

For the Single Player mode ```biking``` is the only available activity, as declared in ```scripts/deploy.ts```

#### Management of Measures

A measure has a ```name``` and can be enabled by setting it to ```allowed```.

For the Single Player mode ```km``` is the only available measure, as declared in ```scripts/deploy.ts```
An activity consists of a ```name``` and the ```oracle``` address. Activities can be enabled by setting it to ```allowed```.

#### Execution of commitment settlement

Expand Down
188 changes: 54 additions & 134 deletions contracts/SinglePlayerCommit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

//TODO Do we want console.log logging, start with Activity constructor?..

contract SinglePlayerCommit is Ownable {
using SafeMath for uint256;

Expand All @@ -21,26 +19,18 @@ contract SinglePlayerCommit is Ownable {
/***************
DATA TYPES
***************/
struct Measure {
string name;
bool allowed;
}

struct Activity {
string name; // e.g. "cycling"
bytes32[] measures; // keys from allowedMeasures
uint256[2][] ranges; // array of [min,max] goal values
string name; // e.g. "cycling" with list scoped to activities supported by Strava
address oracle;
bool allowed;
}

struct Commitment {
address committer; // user
bytes32 activity; // key from allowedActivities
bytes32 measure; // key from allowedMeasures
uint256 goalValue; // must be within range of Activity.measures[measureIndex]
uint256 start;
uint256 end;
bytes32 activityKey;
uint256 goalValue;
uint256 startTime;
uint256 endTime;
uint256 stake; // amount of token staked, scaled by token decimals
bool exists; // flag to help check if commitment exists
uint256 reportedValue; // as reported by oracle
Expand All @@ -52,8 +42,8 @@ contract SinglePlayerCommit is Ownable {
***************/
event NewCommitment(
address committer,
string activity,
string measure,
string activityName,
uint256 goalValue,
uint256 startTime,
uint256 endTime,
uint256 stake
Expand All @@ -68,11 +58,8 @@ contract SinglePlayerCommit is Ownable {
mapping(bytes32 => Activity) public allowedActivities;
bytes32[] public activityList;

mapping(bytes32 => Measure) public allowedMeasures;
bytes32[] public measureList;

mapping(address => Commitment) public commitments; // active commitments
// address[] public committers; // addresses with active commitments
address[] public userCommitments; // addresses with active commitments

mapping(address => uint256) public balances; // current token balances
uint256 public committerBalance; // sum of current token balances
Expand All @@ -82,71 +69,35 @@ contract SinglePlayerCommit is Ownable {
********/
// constructor
constructor(
string memory _activity,
string[] memory _measures,
uint256[2][] memory _ranges,
address _oracle,
string[] memory _activityList,
address _oracleAddress,
address _token
) public {
console.log("Constructor called for SinglePlayerCommit contract");
// set up token interface
token = IERC20(_token);
require(_activityList.length >= 1, "SPC::constructor - activityList empty");

// need to create fixed length bytes32 array to pass to _addActivity
uint256 len = _measures.length;
bytes32[] memory measureKeys = new bytes32[](len);

// register measures
for (uint256 i = 0; i < len; i++) {
// register the measure
bytes32 measureKey = _addMeasure(_measures[i]);
// add its key to the array to be passed to _addActivity
measureKeys[i] = measureKey;
}

// register activity
_addActivity(_activity, measureKeys, _ranges, _oracle);
// register allowed activities with corresponding oracle
_addActivities(_activityList, _oracleAddress);
}

// fallback function (if exists)
// TODO

// view functions

function getActivityName(bytes32 _activityKey) public view returns (string memory) {
return allowedActivities[_activityKey].name;
}

function getActivityMeasures(bytes32 _activityKey) public view returns (string[] memory measureNames) {
bytes32[] memory measures = allowedActivities[_activityKey].measures;
uint256 len = measures.length;
measureNames = new string[](len);
for (uint256 i = 0; i < len; i++) {
measureNames[i] = getMeasureName(measures[i]);
}

return measureNames;
}

function getMeasureName(bytes32 _measureKey) public view returns (string memory) {
return allowedMeasures[_measureKey].name;
}

// other public functions
function depositAndCommit(
bytes32 _activity,
uint256 _measureIndex,
uint256 _goal,
bytes32 _activityKey,
uint256 _goalValue,
uint256 _startTime,
uint256 _stake,
uint256 _depositAmount
) public returns (bool) {
require(deposit(_depositAmount), "SPC::depositAndCommit - deposit failed");

require(
makeCommitment(_activity, _measureIndex, _goal, _startTime, _stake),
"SPC::depositAndCommit - commitment failed"
);
require(makeCommitment(_activityKey, _goalValue, _startTime, _stake), "SPC::depositAndCommit - commitment failed");

return true;
}
Expand All @@ -165,39 +116,31 @@ contract SinglePlayerCommit is Ownable {
}

function makeCommitment(
bytes32 _activity,
uint256 _measureIndex, // index of the Activity.measures array
uint256 _goal,
bytes32 _activityKey,
uint256 _goalValue,
uint256 _startTime,
uint256 _stake
) public returns (bool) {
console.log("makeCommitment called by %s", msg.sender);

require(!commitments[msg.sender].exists, "SPC::makeCommitment - msg.sender already has a commitment");
require(allowedActivities[_activity].allowed, "SPC::makeCommitment - activity doesn't exist or isn't allowed");
require(allowedActivities[_activity].measures.length >= _measureIndex+1, "SPC::makeCommitment - measure index out of bounds");

bytes32 measure = allowedActivities[_activity].measures[_measureIndex];

require(allowedMeasures[measure].allowed, "SPC::makeCommitment - measure doesn't exist or isn't allowed");
require(
allowedActivities[_activityKey].allowed,
"SPC::makeCommitment - activity doesn't exist or isn't allowed"
);
require(_startTime > block.timestamp, "SPC::makeCommitment - commitment cannot start in the past");

uint256[2] storage range = allowedActivities[_activity].ranges[_measureIndex];
require(_goal >= range[0], "SPC::makeCommitment - goal is too low");
require(_goal <= range[1], "SPC::makeCommitment - goal is too high");

require(_goalValue > 1, "SPC::makeCommitment - goal is too low");
require(balances[msg.sender] >= _stake, "SPC::makeCommitment - insufficient token balance");

uint256 endTime = _startTime.add(7 days);

// create commitment...
Commitment memory commitment = Commitment({
committer: msg.sender,
activity: _activity,
measure: measure,
goalValue: _goal,
start: _startTime,
end: endTime,
activityKey: _activityKey,
goalValue: _goalValue,
startTime: _startTime,
endTime: endTime,
stake: _stake,
exists: true,
reportedValue: 0,
Expand All @@ -206,16 +149,8 @@ contract SinglePlayerCommit is Ownable {

// ...and add it to storage
commitments[msg.sender] = commitment;
// committers.push(msg.sender);

emit NewCommitment(
msg.sender,
allowedActivities[_activity].name,
allowedMeasures[measure].name,
_startTime,
endTime,
_stake
);

emit NewCommitment(msg.sender, allowedActivities[_activityKey].name, _goalValue, _startTime, endTime, _stake);

return true;
}
Expand Down Expand Up @@ -247,16 +182,13 @@ contract SinglePlayerCommit is Ownable {
Commitment memory commitment = commitments[committer];

// check if commitment has ended
require(commitment.end < block.timestamp, "SPC::processCommitment - commitment is still active");
require(commitment.endTime < block.timestamp, "SPC::processCommitment - commitment is still active");

bool met = commitment.met;
uint256 stake = commitment.stake;

// "delete" the expired commitment
commitments[committer].exists = false;
// remove the committer from the list of committers
// committers[committer] = committers[committers.length.sub(1)];
// committers.pop();

uint256 penalty;

Expand All @@ -271,58 +203,46 @@ contract SinglePlayerCommit is Ownable {
emit CommitmentEnded(committer, met, penalty);
}

// function processCommitments() public returns (bool) {
// for (uint256 i = 0; i < committers.length; i.add(1)) {
// processCommitment(committers[i]);
// }

// return true;
// }

function ownerWithdraw(uint256 amount) public onlyOwner returns (bool) {
uint256 available = token.balanceOf(address(this)).sub(committerBalance);

require(amount <= available, "SPC::ownerWithdraw - not enough available balance");

require(token.transfer(msg.sender, amount), "SPC::ownerWithdraw - token transfer failed");

return true;
}

// internal functions
function _addActivities(string[] memory _activityList, address oracleAddress) internal {
uint256 arrayLength = _activityList.length;

function _addMeasure(string memory _name) internal returns (bytes32 measureKey) {
Measure memory measure = Measure({ name: _name, allowed: true });

measureKey = keccak256(abi.encode(_name));
allowedMeasures[measureKey] = measure;
measureList.push(measureKey);
for (uint256 i = 0; i < arrayLength; i++) {
_addActivity(_activityList[i], oracleAddress);
}

return measureKey;
console.log("All provided activities added");
}

function _addActivity(
string memory _name,
bytes32[] memory _measures,
uint256[2][] memory _ranges,
address _oracle
) internal returns (bytes32 activityKey) {
uint256 measuresLength = _measures.length;
require(measuresLength == _ranges.length, "SPC::_addActivity - measures and ranges must have same length");

Activity memory activity;

activity.name = _name;
activity.oracle = _oracle;
activity.measures = _measures;
activity.ranges = _ranges;
function _addActivity(string memory _activityName, address _oracleAddress) internal returns (bytes32 activityKey) {
bytes memory activityNameBytes = bytes(_activityName);
require(activityNameBytes.length > 0, "SPC::_addActivity - _activityName empty");

bytes32 _activityKey = keccak256(abi.encode(_activityName));

Activity storage activity = allowedActivities[_activityKey];
activity.name = _activityName;
activity.oracle = _oracleAddress;
activity.allowed = true;

activityKey = keccak256(abi.encode(_name));
allowedActivities[activityKey] = activity;
activityList.push(activityKey);
console.log(
"Registered activity %s, oracle %s, allowed %s",
allowedActivities[_activityKey].name,
allowedActivities[_activityKey].oracle,
allowedActivities[_activityKey].allowed
);

return activityKey;
activityList.push(_activityKey);
return _activityKey;
}

function _changeCommitterBalance(uint256 amount, bool add) internal returns (bool) {
Expand All @@ -340,4 +260,4 @@ contract SinglePlayerCommit is Ownable {

return true;
}
}
}
Loading