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

etherGC.sol +reputationGC.sol #756

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
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
81 changes: 81 additions & 0 deletions contracts/globalConstraints/EtherGC.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
pragma solidity 0.5.17;

import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
import "./GlobalConstraintInterface.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "../controller/Avatar.sol";


/**
* @title EtherGC ether constraint per period
*/
contract EtherGC is GlobalConstraintInterface {
using SafeMath for uint256;

uint256 public periodLength; //the period length in seconds
uint256 public amountAllowedPerPeriod;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this should be percentage-wise, or absolute values. Probably better to keep it absolute.

Avatar public avatar;
uint256 public startTime;
uint256 public avatarBalanceBefore;
// a mapping from period indexes to amounts
mapping(uint256=>uint256) public totalAmountSentPerPeriod;

/**
* @dev initialize
* @param _avatar the avatar to enforce the constraint on
* @param _periodLength the periodLength in seconds
* @param _amountAllowedPerPeriod the amount of eth to constraint for each period
*/
function initialize(
Avatar _avatar,
uint256 _periodLength,
uint256 _amountAllowedPerPeriod
)
external
{
require(avatar == Avatar(0), "can be called only one time");
require(_avatar != Avatar(0), "avatar cannot be zero");
avatar = _avatar;
periodLength = _periodLength;
amountAllowedPerPeriod = _amountAllowedPerPeriod;
// solhint-disable-next-line not-rely-on-time
startTime = now;
}

/**
* @dev check the constraint before the action.
* @return true
*/
function pre(address, bytes32, bytes32) public returns(bool) {
require(msg.sender == avatar.owner(), "only avatar owner is authorize to call");
avatarBalanceBefore = address(avatar).balance;
return true;
}

/**
* @dev check the allowance of ether sent per period
* and throws an error if the constraint is violated
* @return bool which represents a success
*/
function post(address, bytes32, bytes32) public returns(bool) {
require(msg.sender == avatar.owner(), "only avatar owner is authorize to call");
if (avatarBalanceBefore > address(avatar).balance) {
// solhint-disable-next-line not-rely-on-time
uint256 currentPeriodIndex = (now - startTime)/periodLength;
totalAmountSentPerPeriod[currentPeriodIndex] =
totalAmountSentPerPeriod[currentPeriodIndex].add(avatarBalanceBefore.sub(address(avatar).balance));
require(totalAmountSentPerPeriod[currentPeriodIndex] <= amountAllowedPerPeriod,
"Violation of Global constraint EtherGC:amount sent exceed in current period");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should set back avatarBalanceBefore to zero, as it will save gas.

}
avatarBalanceBefore = 0; //save gas
return true;
}

/**
* @dev when return if this globalConstraints is pre, post or both.
* @return CallPhase enum indication Pre, Post or PreAndPost.
*/
function when() public pure returns(GlobalConstraintInterface.CallPhase) {
return GlobalConstraintInterface.CallPhase.PreAndPost;
}
}
2 changes: 1 addition & 1 deletion contracts/globalConstraints/GlobalConstraintInterface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ contract GlobalConstraintInterface {
* @dev when return if this globalConstraints is pre, post or both.
* @return CallPhase enum indication Pre, Post or PreAndPost.
*/
function when() public returns(CallPhase);
function when() public pure returns(CallPhase);
}
109 changes: 109 additions & 0 deletions contracts/globalConstraints/ReputationGC.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
pragma solidity 0.5.17;

import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
import "./GlobalConstraintInterface.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "../controller/Avatar.sol";


/**
* @title ReputationGC reputation mint/butn constraint per period
*/
contract ReputationGC is GlobalConstraintInterface {
using SafeMath for uint256;

uint256 public periodLength; //the period length in seconds
uint256 public percentageAllowedPerPeriod;
Avatar public avatar;
uint256 public startTime;
uint256 public totalRepSupplyBefore;

// a mapping from period indexes to amounts
mapping(uint256=>uint256) public totalRepMintedPerPeriod;
mapping(uint256=>uint256) public totalRepBurnedPerPeriod;
// a mapping from period to totalSupply
mapping(uint256=>uint256) public totalRepSupplyPerPeriod;

/**
* @dev initialize
* @param _avatar the avatar to enforce the constraint on
* @param _periodLength the periodLength in seconds
* @param _percentageAllowedPerPeriod the amount of reputation to constraint for each period (brun and mint)
*/
function initialize(
Avatar _avatar,
uint256 _periodLength,
uint256 _percentageAllowedPerPeriod
)
external
{
require(avatar == Avatar(0), "can be called only one time");
require(_avatar != Avatar(0), "avatar cannot be zero");
require(_percentageAllowedPerPeriod <= 100, "precentage allowed cannot be greated than 100");
avatar = _avatar;
periodLength = _periodLength;
percentageAllowedPerPeriod = _percentageAllowedPerPeriod;
// solhint-disable-next-line not-rely-on-time
startTime = now;
}

/**
* @dev check the constraint before the action.
* @return true
*/
function pre(address, bytes32, bytes32) public returns(bool) {
require(msg.sender == avatar.owner(), "only avatar owner is authorize to call");
totalRepSupplyBefore = (avatar.nativeReputation()).totalSupply();
// solhint-disable-next-line not-rely-on-time
uint256 currentPeriodIndex = (now - startTime)/periodLength;
if (totalRepSupplyPerPeriod[currentPeriodIndex] == 0) {
totalRepSupplyPerPeriod[currentPeriodIndex] = totalRepSupplyBefore;
}

return true;
}

/**
* @dev check the allowance of reputation minted or burned per period
* and throws an error if the constraint is violated
* @return bool which represents a success
*/
function post(address, bytes32, bytes32) public returns(bool) {
require(msg.sender == avatar.owner(), "only avatar owner is authorize to call");
uint256 currentRepTotalSupply = (avatar.nativeReputation()).totalSupply();
if (totalRepSupplyBefore != currentRepTotalSupply) {
// solhint-disable-next-line not-rely-on-time
uint256 currentPeriodIndex = (now - startTime)/periodLength;
uint256 repAllowedForCurrentPeriod = totalRepSupplyPerPeriod[currentPeriodIndex]
.mul(percentageAllowedPerPeriod)
.div(100);

if (totalRepSupplyBefore > currentRepTotalSupply) {
//reputation was burned
uint256 burnedReputation = totalRepSupplyBefore.sub(currentRepTotalSupply);
totalRepBurnedPerPeriod[currentPeriodIndex] =
totalRepBurnedPerPeriod[currentPeriodIndex].add(burnedReputation);

require(totalRepBurnedPerPeriod[currentPeriodIndex] <= repAllowedForCurrentPeriod,
"Violation of Global constraint ReputationGC:amount of reputation burned exceed in current period");
} else {
// reputation was minted
uint256 mintedReputation = currentRepTotalSupply.sub(totalRepSupplyBefore);
totalRepMintedPerPeriod[currentPeriodIndex] =
totalRepMintedPerPeriod[currentPeriodIndex].add(mintedReputation);
require(totalRepMintedPerPeriod[currentPeriodIndex] <= repAllowedForCurrentPeriod,
"Violation of Global constraint ReputationGC:amount of reputation minted exceed in current period");
}
}
totalRepSupplyBefore = 0; //save gas
return true;
}

/**
* @dev when return if this globalConstraints is pre, post or both.
* @return CallPhase enum indication Pre, Post or PreAndPost.
*/
function when() public pure returns(GlobalConstraintInterface.CallPhase) {
return GlobalConstraintInterface.CallPhase.PreAndPost;
}
}
53 changes: 53 additions & 0 deletions contracts/schemes/GlobalConstraintAddOrRemove.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
pragma solidity 0.5.17;

import "../controller/Controller.sol";

/**
* @title A scheme for adding or removing a global constraint
* The scheme will unregister itself after register the globalConstraint
* This scheme should be register to the dao with permission 0x00000004
*/

contract GlobalConstraintAddOrRemove {
ben-kaufman marked this conversation as resolved.
Show resolved Hide resolved

Avatar public avatar;
address public globalConstraint;
bytes32 public paramsHash;

/**
* @dev initialize
* @param _avatar the avatar to mint reputation from
* @param _globalConstraint the globalConstraint address
* @param _paramsHash globalConstraint paramsHash
*/
function initialize(
Avatar _avatar,
address _globalConstraint,
bytes32 _paramsHash
) external {
require(avatar == Avatar(0), "can be called only one time");
require(_avatar != Avatar(0), "avatar cannot be zero");
avatar = _avatar;
globalConstraint = _globalConstraint;
paramsHash = _paramsHash;
}

/**
* @dev add globalConstraint
* and remove itsef.
*/
function add() external {
Controller(avatar.owner()).addGlobalConstraint(globalConstraint, paramsHash, address(avatar));
Controller(avatar.owner()).unregisterSelf(address(avatar));
}

/**
* @dev remove globalConstraint
* and remove itsef.
*/
function remove() external {
Controller(avatar.owner()).removeGlobalConstraint(globalConstraint, address(avatar));
Controller(avatar.owner()).unregisterSelf(address(avatar));
}

}
113 changes: 113 additions & 0 deletions test/etherGC.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import * as helpers from './helpers';
const DAOToken = artifacts.require("./DAOToken.sol");
const EtherGC = artifacts.require('./globalConstraints/EtherGC.sol');
const Controller = artifacts.require("./Controller.sol");
const Reputation = artifacts.require("./Reputation.sol");
const Avatar = artifacts.require("./Avatar.sol");
const ActionMock = artifacts.require("./ActionMock.sol");
var constants = require('../test/constants');


let reputation, avatar,token,controller,etherGC;
var periodLengthConst = 1000;
const setup = async function () {
token = await DAOToken.new("TEST","TST",0);
// set up a reputation system
reputation = await Reputation.new();
avatar = await Avatar.new('name', token.address, reputation.address);
controller = await Controller.new(avatar.address,{gas: constants.ARC_GAS_LIMIT});
await avatar.transferOwnership(controller.address);
etherGC = await EtherGC.new();
await etherGC.initialize(avatar.address,periodLengthConst,web3.utils.toWei('5', "ether")); //periodLengthConst seconds ,5 eth
};

contract('EtherGC', accounts => {
it("initialize", async () => {
await setup();
assert.equal(await etherGC.avatar(),avatar.address);
assert.equal(await etherGC.amountAllowedPerPeriod(),web3.utils.toWei('5', "ether"));
assert.equal(await etherGC.periodLength(),1000);
});

it("send ether check", async () => {

await setup();
try {
await etherGC.initialize(avatar.address,periodLengthConst,web3.utils.toWei('5', "ether")); //periodLengthConst seconds ,5 eth
assert(false,"cannpt init twice ");
} catch(ex){
helpers.assertVMException(ex);
}
var startTime = await etherGC.startTime();

await controller.addGlobalConstraint(etherGC.address,helpers.NULL_HASH,avatar.address);
//move 10 ether to avatar
await web3.eth.sendTransaction({from:accounts[0],to:avatar.address, value: web3.utils.toWei('10', "ether")});
await controller.sendEther(web3.utils.toWei('1', "ether"), accounts[2],avatar.address);
await controller.sendEther(web3.utils.toWei('4', "ether"), accounts[2],avatar.address);

try {
await controller.sendEther(web3.utils.toWei('1', "ether"), accounts[2],avatar.address);
assert(false,"sendEther should fail due to the etherGC global constraint ");
}
catch(ex){
helpers.assertVMException(ex);
}
helpers.increaseTime(periodLengthConst+1);
await controller.sendEther(web3.utils.toWei('1', "ether"), accounts[2],avatar.address);
await controller.sendEther(web3.utils.toWei('4', "ether"), accounts[2],avatar.address);
await web3.eth.sendTransaction({from:accounts[0],to:avatar.address, value: web3.utils.toWei('10', "ether")});
var diff = ((await web3.eth.getBlock("latest")).timestamp - startTime.toNumber())% periodLengthConst;
//increment time for next period
helpers.increaseTime(periodLengthConst-diff);
await controller.sendEther(web3.utils.toWei('4', "ether"), accounts[2],avatar.address);
});

it("genericCall check", async () => {

await setup();
try {
await etherGC.initialize(avatar.address,periodLengthConst,web3.utils.toWei('5', "ether")); //periodLengthConst seconds ,5 eth
assert(false,"cannpt init twice ");
} catch(ex){
helpers.assertVMException(ex);
}
var startTime = await etherGC.startTime();

await controller.addGlobalConstraint(etherGC.address,helpers.NULL_HASH,avatar.address);
//move 10 ether to avatar
await web3.eth.sendTransaction({from:accounts[0],to:avatar.address, value: web3.utils.toWei('10', "ether")});

let actionMock = await ActionMock.new();
let a = 7;
let b = actionMock.address;
let c = "0x1234";
const encodeABI = await new web3.eth.Contract(actionMock.abi).methods.test(a,b,c).encodeABI();
await controller.genericCall(actionMock.address,encodeABI,avatar.address,web3.utils.toWei('1', "ether"));
await controller.genericCall(actionMock.address,encodeABI,avatar.address,web3.utils.toWei('4', "ether"));

try {
await controller.sendEther(web3.utils.toWei('1', "ether"), accounts[2],avatar.address);
assert(false,"sendEther should fail due to the etherGC global constraint ");
}
catch(ex){
helpers.assertVMException(ex);
}

try {
await controller.genericCall(actionMock.address,encodeABI,avatar.address,web3.utils.toWei('1', "ether"));
assert(false,"sendEther should fail due to the etherGC global constraint ");
}
catch(ex){
helpers.assertVMException(ex);
}
helpers.increaseTime(periodLengthConst+1);
await controller.genericCall(actionMock.address,encodeABI,avatar.address,web3.utils.toWei('1', "ether"));
await controller.genericCall(actionMock.address,encodeABI,avatar.address,web3.utils.toWei('4', "ether"));
await web3.eth.sendTransaction({from:accounts[0],to:avatar.address, value: web3.utils.toWei('10', "ether")});
var diff = ((await web3.eth.getBlock("latest")).timestamp - startTime.toNumber())% periodLengthConst;
//increment time for next period
helpers.increaseTime(periodLengthConst-diff);
await controller.genericCall(actionMock.address,encodeABI,avatar.address,web3.utils.toWei('4', "ether"));
});
});
Loading