Skip to content

Commit

Permalink
init repo
Browse files Browse the repository at this point in the history
  • Loading branch information
Aodhgan committed Apr 16, 2021
0 parents commit 7869b05
Show file tree
Hide file tree
Showing 26 changed files with 10,690 additions and 0 deletions.
24 changes: 24 additions & 0 deletions .circleci/config.yml
@@ -0,0 +1,24 @@
version: 2.1
orbs:
coveralls: coveralls/coveralls@1.0.6
jobs:
build-and-test:
docker:
- image: 'circleci/node:14.15.3'
steps:
- checkout
- run:
name: Install and Test
no_output_timeout: 30m
command: yarn && yarn hint && yarn coverage
- coveralls/upload
- store_artifacts:
path: coverage
- store_artifacts:
path: test-results.xml
- store_artifacts:
path: coverage.json
workflows:
build-and-test:
jobs:
- build-and-test
21 changes: 21 additions & 0 deletions .gitignore
@@ -0,0 +1,21 @@
#Builder files
cache
.cache-openzeppelin
artifacts
.history
build
.build-openzeppelin
node_modules
dist
coverage
coverage.json
.envrc
.openzeppelin/dev-*
.openzeppelin/.session
.oz-migrate/dev-*
abis
test-results.xml
.DS_Store
deployments/local*
Networks.md
deployments/fork*
1 change: 1 addition & 0 deletions .nvmrc
@@ -0,0 +1 @@
v14.15.3
12 changes: 12 additions & 0 deletions .solcover.js
@@ -0,0 +1,12 @@
module.exports = {
mocha: { reporter: 'mocha-junit-reporter' },
providerOptions: {
network_id: 1337,
_chainId: 1337,
_chainIdRpc: 1337
},
skipFiles: [
"external",
"test"
]
};
19 changes: 19 additions & 0 deletions .solhint.json
@@ -0,0 +1,19 @@
{
"extends": "solhint:recommended",
"plugins": [],
"rules": {
"func-order": "off",
"mark-callable-contracts": "off",
"no-empty-blocks": "off",
"compiler-version": ["error", ">=0.6.0 <0.8.0"],
"private-vars-leading-underscore": "off",
"code-complexity": "warn",
"const-name-snakecase": "warn",
"function-max-lines": "warn",
"max-line-length": ["warn", 160],
"avoid-suicide": "error",
"avoid-sha3": "warn",
"not-rely-on-time": "off",
"reason-string": ["warn", {"maxLength": 64}]
}
}
38 changes: 38 additions & 0 deletions README.md
@@ -0,0 +1,38 @@
# pooltogether-pods-operations

[![Coverage Status](https://coveralls.io/repos/github/pooltogether/pooltogether-operations-contracts/badge.svg?branch=main)](https://coveralls.io/github/pooltogether/pooltogether-operations-contracts?branch=main)


PoolTogether Operations contracts is PoolTogether's integration with ChainLinks upkeep system for pods.

## How it works

The goal of this system is to fully automate the awarding of the PoolTogether governance owned prize pools.

A registry of these prize pools exists (as an Ownable MappedSinglyLinkedList) and the prize strategy for each prize pool checked every block (`canStartAward()` and `canCompleteAward()`) to see if upkeep is required.

If upkeep is required then either `startAward()` or `completeAward()` are called on the prize pool.

To prevent out-of-gas situations, a prize pool upkeep batch size is defined in the constructor.

The upkeepers performing the upkeep are compensated in LINK so the PrizeStrategyUpkeep contact needs to maintain a balance of LINK.

### Registry Interface



# Installation
Install the repo and dependencies by running:
`yarn`

## Deployment
These contracts can be deployed to a network by running:
`yarn deploy <networkName>`

# Testing
Run the unit tests locally with:
`yarn test`

## Coverage
Generate the test coverage report with:
`yarn coverage`
52 changes: 52 additions & 0 deletions contracts/PrizePoolRegistry.sol
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.7.6;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/access/Ownable.sol";

import "./interfaces/KeeperCompatibleInterface.sol";
import "./interfaces/PeriodicPrizeStrategyInterface.sol";
import "./interfaces/PrizePoolInterface.sol";
import "./utils/MappedSinglyLinkedList.sol";



///@notice A registry to hold Prize Pool addresses. Underlying data structure is a singly linked list.
contract PrizePoolRegistry is Ownable {

using MappedSinglyLinkedList for MappedSinglyLinkedList.Mapping;

event PrizePoolAdded(address indexed prizePool);
event PrizePoolRemoved(address indexed prizePool);

MappedSinglyLinkedList.Mapping internal prizePoolList;

constructor() Ownable(){
prizePoolList.initialize();
}


/// @notice Returns an array of all prizePools in the linked list
///@return Array of prize pool addresses
function getPrizePools() view external returns(address[] memory){
return prizePoolList.addressArray();
}

/// @notice Adds addresses to the linked list. Will revert if the address is already in the list. Can only be called by the Registry owner.
/// @param _prizePools Array of prizePool addresses
function addPrizePools(address[] calldata _prizePools) public onlyOwner {
for(uint256 prizePool = 0; prizePool < _prizePools.length; prizePool++ ){
prizePoolList.addAddress(_prizePools[prizePool]);
emit PrizePoolAdded(_prizePools[prizePool]);
}
}

/// @notice Removes an address from the linked list. Can only be called by the Registry owner.
/// @param _previousPrizePool The address positionally localed before the address that will be deleted. This may be the SENTINEL address if the list contains one prize pool address
/// @param _prizePool The address to remove from the linked list.
function removePrizePool(address _previousPrizePool, address _prizePool) public onlyOwner{
prizePoolList.removeAddress(_previousPrizePool, _prizePool);
emit PrizePoolRemoved(_prizePool);
}
}
79 changes: 79 additions & 0 deletions contracts/PrizeStrategyUpkeep.sol
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.7.6;
pragma experimental ABIEncoderV2;


import "./interfaces/KeeperCompatibleInterface.sol";
import "./interfaces/PeriodicPrizeStrategyInterface.sol";
import "./interfaces/PrizePoolRegistryInterface.sol";
import "./interfaces/PrizePoolInterface.sol";
import "./utils/SafeAwardable.sol";


///@notice Contract implements Chainlink's Upkeep system interface, automating the upkeep of PrizePools in the associated registry.
contract PrizeStrategyUpkeep is KeeperCompatibleInterface {

using SafeAwardable for address;

address public prizePoolRegistry;

uint public upkeepBatchSize;

constructor(address _prizePoolRegistry, uint256 _upkeepBatchSize) public {
prizePoolRegistry = _prizePoolRegistry;
upkeepBatchSize = _upkeepBatchSize;
}

/// @notice Checks if PrizePools require upkeep. Call in a static manner every block by the Chainlink Upkeep network.
/// @param checkData Not used in this implementation.
/// @return upkeepNeeded as true if performUpkeep() needs to be called, false otherwise. performData returned empty.
function checkUpkeep(bytes calldata checkData) view override external returns (bool upkeepNeeded, bytes memory performData){ // check view

address[] memory prizePools = PrizePoolRegistryInterface(prizePoolRegistry).getPrizePools();

// check if canStartAward()
for(uint256 pool = 0; pool < prizePools.length; pool++){
address prizeStrategy = PrizePoolInterface(prizePools[pool]).prizeStrategy();
if(prizeStrategy.canStartAward()){
return (true, performData);
}
}
// check if canCompleteAward()
for(uint256 pool = 0; pool < prizePools.length; pool++){
address prizeStrategy = PrizePoolInterface(prizePools[pool]).prizeStrategy();
if(prizeStrategy.canCompleteAward()){
return (true, performData);
}
}
return (false, performData);
}
/// @notice Performs upkeep on the prize pools.
/// @param performData Not used in this implementation.
function performUpkeep(bytes calldata performData) override external{

address[] memory prizePools = PrizePoolRegistryInterface(prizePoolRegistry).getPrizePools();

uint256 batchCounter = upkeepBatchSize; //counter for batch
uint256 poolIndex = 0;

while(batchCounter > 0 && poolIndex < prizePools.length){

address prizeStrategy = PrizePoolInterface(prizePools[poolIndex]).prizeStrategy();

if(prizeStrategy.canStartAward()){
PeriodicPrizeStrategyInterface(prizeStrategy).startAward();
batchCounter--;
}
else if(prizeStrategy.canCompleteAward()){
PeriodicPrizeStrategyInterface(prizeStrategy).completeAward();
batchCounter--;
}
poolIndex++;
}

}

}


51 changes: 51 additions & 0 deletions contracts/interfaces/KeeperCompatibleInterface.sol
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.7.6;

interface KeeperCompatibleInterface {

/**
* @notice method that is simulated by the keepers to see if any work actually
* needs to be performed. This method does does not actually need to be
* executable, and since it is only ever simulated it can consume lots of gas.
* @dev To ensure that it is never called, you may want to add the
* cannotExecute modifier from KeeperBase to your implementation of this
* method.
* @param checkData specified in the upkeep registration so it is always the
* same for a registered upkeep. This can easily be broken down into specific
* arguments using `abi.decode`, so multiple upkeeps can be registered on the
* same contract and easily differentiated by the contract.
* @return upkeepNeeded boolean to indicate whether the keeper should call
* performUpkeep or not.
* @return performData bytes that the keeper should call performUpkeep with, if
* upkeep is needed. If you would like to encode data to decode later, try
* `abi.encode`.
*/
function checkUpkeep(
bytes calldata checkData
)
external
returns (
bool upkeepNeeded,
bytes memory performData
);
/**
* @notice method that is actually executed by the keepers, via the registry.
* The data returned by the checkUpkeep simulation will be passed into
* this method to actually be executed.
* @dev The input to this method should not be trusted, and the caller of the
* method should not even be restricted to any single registry. Anyone should
* be able call it, and the input should be validated, there is no guarantee
* that the data passed in is the performData returned from checkUpkeep. This
* could happen due to malicious keepers, racing keepers, or simply a state
* change while the performUpkeep transaction is waiting for confirmation.
* Always validate the data passed in.
* @param performData is the data which was passed back from the checkData
* simulation. If it is encoded, it can easily be decoded into other types by
* calling `abi.decode`. This data should not be trusted, and should be
* validated against the contract's current state.
*/
function performUpkeep(
bytes calldata performData
) external;
}
9 changes: 9 additions & 0 deletions contracts/interfaces/PeriodicPrizeStrategyInterface.sol
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;

interface PeriodicPrizeStrategyInterface {
function startAward() external;
function completeAward() external;
function canStartAward() external view returns (bool);
function canCompleteAward() external view returns (bool);
}
7 changes: 7 additions & 0 deletions contracts/interfaces/PrizePoolInterface.sol
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.7.6;

interface PrizePoolInterface {
function prizeStrategy() external view returns (address);
}
7 changes: 7 additions & 0 deletions contracts/interfaces/PrizePoolRegistryInterface.sol
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.7.6;

interface PrizePoolRegistryInterface {
function getPrizePools() external view returns(address[] memory);
}
40 changes: 40 additions & 0 deletions contracts/test/MappedSinglyLinkedListExposed.sol
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.7.6;

import "../utils/MappedSinglyLinkedList.sol";

contract MappedSinglyLinkedListExposed {
using MappedSinglyLinkedList for MappedSinglyLinkedList.Mapping;

MappedSinglyLinkedList.Mapping public list;

function initialize() external {
list.initialize();
}

function addressArray() external view returns (address[] memory) {
return list.addressArray();
}

function addAddresses(address[] calldata addresses) external {
list.addAddresses(addresses);
}

function addAddress(address newAddress) external {
list.addAddress(newAddress);
}

function removeAddress(address prevAddress, address addr) external {
list.removeAddress(prevAddress, addr);
}

function contains(address addr) external view returns (bool) {
return list.contains(addr);
}

function clearAll() external {
list.clearAll();
}

}

0 comments on commit 7869b05

Please sign in to comment.