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
77 changes: 70 additions & 7 deletions contracts/schemes/ReputationFromToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,34 @@ pragma solidity ^0.5.11;
import "../controller/ControllerInterface.sol";
import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
import "./CurveInterface.sol";
import "openzeppelin-solidity/contracts/cryptography/ECDSA.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";

/**
* @title A scheme for reputation allocation according to token balances
* This contract is assuming that the token contract is paused, and one cannot transfer its tokens.
*/

contract ReputationFromToken {
using ECDSA for bytes32;
using SafeMath for uint256;

IERC20 public tokenContract;
CurveInterface public curve;
// beneficiary -> bool
mapping(address => bool) public redeems;
Avatar public avatar;

event Redeem(address indexed _beneficiary, address indexed _sender, uint256 _amount);
// Digest describing the data the user signs according EIP 712.
// Needs to match what is passed to Metamask.
bytes32 public constant DELEGATION_HASH_EIP712 =
keccak256(abi.encodePacked(
"address ReputationFromTokenAddress",
"address Beneficiary"
));

event Redeem(address indexed _beneficiary, address indexed _sender, uint256 _amount);

/**
* @dev initialize
* @param _avatar the avatar to mint reputation from
Expand All @@ -38,22 +50,73 @@ contract ReputationFromToken {
* @param _beneficiary the beneficiary address to redeem for
* @return uint256 minted reputation
*/
function redeem(address _beneficiary) public returns(uint256) {
function redeem(address _beneficiary) external returns(uint256) {
return _redeem(_beneficiary, msg.sender);
}

/**
* @dev redeemWithSignature function
* @param _beneficiary the beneficiary address to redeem for
* @param _signatureType signature type
1 - for web3.eth.sign
2 - for eth_signTypedData according to EIP #712.
* @param _signature - signed data by the staker
* @return uint256 minted reputation
*/
function redeemWithSignature(
address _beneficiary,
uint256 _signatureType,
bytes calldata _signature
)
external
returns(uint256)
{
// Recreate the digest the user signed
bytes32 delegationDigest;
if (_signatureType == 2) {
delegationDigest = keccak256(
abi.encodePacked(
DELEGATION_HASH_EIP712, keccak256(
abi.encodePacked(
address(this),
_beneficiary)
)
)
);
} else {
delegationDigest = keccak256(
abi.encodePacked(
address(this),
_beneficiary)
).toEthSignedMessageHash();
}
address redeemer = delegationDigest.recover(_signature);
require(redeemer != address(0), "redeemer address cannot be 0");
return _redeem(_beneficiary, redeemer);
}

/**
* @dev redeem function
* @param _beneficiary the beneficiary address to redeem for
* @param _redeemer the redeemer address
* @return uint256 minted reputation
*/
function _redeem(address _beneficiary, address _redeemer) private returns(uint256) {
require(avatar != Avatar(0), "should initialize first");
require(redeems[msg.sender] == false, "redeeming twice from the same account is not allowed");
redeems[msg.sender] = true;
uint256 tokenAmount = tokenContract.balanceOf(msg.sender);
require(redeems[_redeemer] == false, "redeeming twice from the same account is not allowed");
redeems[_redeemer] = true;
uint256 tokenAmount = tokenContract.balanceOf(_redeemer);
if (curve != CurveInterface(0)) {
tokenAmount = curve.calc(tokenAmount);
}
if (_beneficiary == address(0)) {
_beneficiary = msg.sender;
_beneficiary = _redeemer;
}
require(
ControllerInterface(
avatar.owner())
.mintReputation(tokenAmount, _beneficiary, address(avatar)), "mint reputation should succeed");
emit Redeem(_beneficiary, msg.sender, tokenAmount);
emit Redeem(_beneficiary, _redeemer, tokenAmount);
return tokenAmount;
}
}
38 changes: 36 additions & 2 deletions test/reputationfromtoken.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ var PolkaCurve = artifacts.require("./PolkaCurve.sol");

var NectarRepAllocation = artifacts.require("./NectarRepAllocation.sol");
const NectarToken = artifacts.require('./Reputation.sol');

var ethereumjs = require('ethereumjs-abi');

const setupNectar = async function (accounts) {
var testSetup = new helpers.TestSetup();
Expand Down Expand Up @@ -66,7 +66,25 @@ const setup = async function (accounts, _initialize = true) {
await testSetup.daoCreator.setSchemes(testSetup.org.avatar.address,[testSetup.reputationFromToken.address],[helpers.NULL_HASH],[permissions],"metaData");
return testSetup;
};

const signatureType = 1;
const redeem = async function(_testSetup,_beneficiary,_redeemer,_fromAccount) {
var textMsg = "0x"+ethereumjs.soliditySHA3(
["address","address"],
[_testSetup.reputationFromToken.address, _beneficiary]
).toString("hex");
//https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethsign
let signature = await web3.eth.sign(textMsg , _redeemer);
const signature1 = signature.substring(0, signature.length-2);
var v = signature.substring(signature.length-2, signature.length);

if (v === '00') {
signature = signature1+'1b';
} else {
signature = signature1+'1c';
}
return (await _testSetup.reputationFromToken.redeemWithSignature(_beneficiary,signatureType,signature
,{from:_fromAccount}));
};
contract('ReputationFromToken and RepAllocation', accounts => {
it("initialize", async () => {
let testSetup = await setup(accounts);
Expand Down Expand Up @@ -132,6 +150,22 @@ contract('ReputationFromToken and RepAllocation', accounts => {
assert.equal(await testSetup.org.reputation.balanceOf(accounts[1]),expected);
});

it("redeemWithSignature", async () => {
let testSetup = await setup(accounts);
var tx = await redeem(testSetup,accounts[1],accounts[0],accounts[2]);
var total_reputation = await testSetup.curve.TOTAL_REPUTATION();
var sum_of_sqrt = await testSetup.curve.SUM_OF_SQRTS();
var expected = Math.floor(((10*total_reputation)/sum_of_sqrt) * 1000000000) * 1000000000;

assert.equal(tx.logs.length,1);
assert.equal(tx.logs[0].event,"Redeem");
assert.equal(tx.logs[0].args._beneficiary,accounts[1]);
assert.equal(tx.logs[0].args._amount.toString(),expected);
assert.equal(tx.logs[0].args._sender,accounts[0]);
assert.equal(await testSetup.org.reputation.balanceOf(accounts[0]),1000);
assert.equal(await testSetup.org.reputation.balanceOf(accounts[1]),expected);
});

it("redeem with no beneficiary", async () => {
let testSetup = await setup(accounts);
var tx = await testSetup.reputationFromToken.redeem(helpers.NULL_ADDRESS);
Expand Down