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 6 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
78 changes: 78 additions & 0 deletions contracts/globalConstraints/EtherGC.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
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 blocks units
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 startBlock;
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 blocks units
Copy link
Contributor

Choose a reason for hiding this comment

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

I think period should not be in blocks, as block time is changing.
The timestamp cannot be trusted up to seconds, but it can be for large periods, as in this case.

* @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;
startBlock = block.number;
Copy link
Contributor

Choose a reason for hiding this comment

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

Start time/block is actually not really required, as you can pick any point in time to count from. But it makes the mapping more readable (start from index 0 instead of a random shift). Guessing it is worth the one-time 20k gas.

}

/**
* @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) {
uint256 currentPeriodIndex = (block.number - startBlock)/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.

}
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);
}
96 changes: 96 additions & 0 deletions contracts/globalConstraints/ReputationGC.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
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 blocks units
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as in EtherGC

uint256 public percentageAllowedPerPeriod;
Avatar public avatar;
uint256 public startBlock;
uint256 public totalRepSupplyBefore;
// a mapping from period indexes to amounts
mapping(uint256=>uint256) public totalRepMintedPerPeriod;
mapping(uint256=>uint256) public totalRepBurnedPerPeriod;

/**
* @dev initialize
* @param _avatar the avatar to enforce the constraint on
* @param _periodLength the periodLength in blocks units
* @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;
startBlock = block.number;
}

/**
* @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();
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) {
uint256 currentPeriodIndex = (block.number - startBlock)/periodLength;
uint256 periodBlockReference = startBlock + (currentPeriodIndex * periodLength);
uint256 repAllowedForCurrentPeriod =
((avatar.nativeReputation()).totalSupplyAt(periodBlockReference)).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 if (totalRepSupplyBefore < currentRepTotalSupply) {
// 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");
}
}
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));
}

}
137 changes: 137 additions & 0 deletions test/etherGC.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
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;
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,10,web3.utils.toWei('5', "ether")); //10 blocks ,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(),10);
});

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

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

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);
}
var i;
for (i = 0 ;i< 10;i++) {
//increment 10 blocks in ganache
//use mint rep to increment blocks number in ganache.
tx = await reputation.mint(accounts[0],1);
}
await controller.sendEther(web3.utils.toWei('1', "ether"), accounts[2],avatar.address);

var tx = await controller.sendEther(web3.utils.toWei('4', "ether"), accounts[2],avatar.address);
var periodIndex = Math.floor((tx.receipt.blockNumber - startBlock.toNumber())/10);
await web3.eth.sendTransaction({from:accounts[0],to:avatar.address, value: web3.utils.toWei('10', "ether")});

for (i = 0 ;i< 10;i++) {
//increment 10 blocks or till the period index is changed (in ganache)
//use mint rep to increment blocks number in ganache.
tx = await reputation.mint(accounts[0],1);
if (Math.floor((tx.receipt.blockNumber - startBlock.toNumber())/10) !== periodIndex) {
break;
}
}
await controller.sendEther(web3.utils.toWei('4', "ether"), accounts[2],avatar.address);
});

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

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

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);
}
var i;
for (i = 0 ;i< 10;i++) {
//increment 10 blocks in ganache
//use mint rep to increment blocks number in ganache.
tx = await reputation.mint(accounts[0],1);
}
await controller.genericCall(actionMock.address,encodeABI,avatar.address,web3.utils.toWei('1', "ether"));
var tx = await controller.genericCall(actionMock.address,encodeABI,avatar.address,web3.utils.toWei('1', "ether"));
var periodIndex = Math.floor((tx.receipt.blockNumber - startBlock.toNumber())/10);
await web3.eth.sendTransaction({from:accounts[0],to:avatar.address, value: web3.utils.toWei('10', "ether")});

for (i = 0 ;i< 10;i++) {
//increment 10 blocks or till the period index is changed (in ganache)
//use mint rep to increment blocks number in ganache.
tx = await reputation.mint(accounts[0],1);
if (Math.floor((tx.receipt.blockNumber - startBlock.toNumber())/10) !== periodIndex) {
break;
}
}
await controller.genericCall(actionMock.address,encodeABI,avatar.address,web3.utils.toWei('1', "ether"));
});
});
Loading