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

Add fee manager for erc-to-erc bridge in POSDAO chain #166

Merged
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
04ab82f
Add fee manager erc-to-erc
patitonar Mar 26, 2019
4cfb617
Add erc-to-erc fee manager unit tests
patitonar Mar 26, 2019
96d75f2
Add rewardable erc-to-erc executeAffirmation tests
patitonar Mar 29, 2019
0092b10
Fix truffle version
patitonar Mar 29, 2019
bf2222d
Add fee manager on erc-to-erc deploy script
patitonar Mar 29, 2019
88a503b
Use ERC677BridgeTokenRewardable if rewards distributed on erc-to-erc
patitonar Apr 1, 2019
5400cd9
Add erc-to-erc reward management schema
patitonar Apr 1, 2019
4def1fd
Merge branch '119-Epic-rewards-for-bridge-validators' into 160-fee-er…
patitonar Apr 3, 2019
0ae69e6
Fix package-lock.json
patitonar Apr 3, 2019
05d5a2b
Fix merge conflicts
patitonar Apr 3, 2019
2bc4bd7
Merge branch '119-Epic-rewards-for-bridge-validators' into 160-fee-er…
patitonar Apr 16, 2019
16be3f8
Add event on fee distribution
patitonar Apr 16, 2019
cf81b76
Refactor BaseFeeManager
patitonar Apr 16, 2019
95f2362
Fix _initialize on HomeBridgeErcToErc
patitonar Apr 16, 2019
63fbd12
Move ValidatorsFeeManager constants
patitonar Apr 16, 2019
8ab76b9
Add POSDAOHomeBridgeErcToErc
patitonar Apr 17, 2019
851c0b9
Fix erc_to_erc home deploy script
patitonar Apr 17, 2019
1ca930a
Update erc-to-erc rewards schemas
patitonar Apr 18, 2019
d5c7c5a
Fix erc-to-erc reward schema text
patitonar Apr 22, 2019
3592024
Merge branch '119-Epic-rewards-for-bridge-validators' into 160-fee-er…
patitonar Apr 22, 2019
6b25ffb
Update fee events logic on HomeBridgeErcToErc
patitonar Apr 22, 2019
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
10 changes: 10 additions & 0 deletions REWARD_MANAGEMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,13 @@ Fees are calculated and distributed on Home network. Validators will receive nat
### Home to Foreign transfer
Fees are calculated and distributed on Home network. Validators will receive native coins.
![erc-native-hometoforeign](https://user-images.githubusercontent.com/4614574/51607508-96f47480-1ef3-11e9-93a1-0f1111793f2a.png)

## ERC-TO-ERC

### Foreign to Home transfer
Fees are calculated and distributed on Home network. Validators will receive ERC20 tokens.
![ERC-ERC-ForeignToHome](https://user-images.githubusercontent.com/4614574/55352936-70752b80-5498-11e9-98ec-f2ca4d9fcb8b.png)

### Home to Foreign transfer
Fees are calculated and distributed on Home network. Validators will receive ERC20 tokens.
![ERC-ERC-HomeToForeign](https://user-images.githubusercontent.com/4614574/55352955-7d921a80-5498-11e9-92f5-8391952d1f78.png)
1 change: 1 addition & 0 deletions contracts/IBlockReward.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ interface IBlockReward {
function mintedTotally() public view returns (uint256);
function mintedTotallyByBridge(address _bridge) public view returns(uint256);
function bridgesAllowedLength() external view returns(uint256);
function addBridgeTokenFeeReceivers(uint256 _amount) external;
}
39 changes: 39 additions & 0 deletions contracts/test/BlockReward.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import "../libraries/SafeMath.sol";
contract BlockReward is IBlockReward {
using SafeMath for uint256;

address[] public validatorList;
uint256 public mintedCoins = 0;
uint256 public feeAmount = 0;
mapping(bytes32 => uint256) internal uintStorage;
bytes32 internal constant MINTED_TOTALLY_BY_BRIDGE = "mintedTotallyByBridge";
address public token;

function () external payable {
}
Expand Down Expand Up @@ -40,4 +43,40 @@ contract BlockReward is IBlockReward {
bytes32 hash = keccak256(abi.encode(MINTED_TOTALLY_BY_BRIDGE, _bridge));
uintStorage[hash] = uintStorage[hash].add(_amount);
}

function setValidatorsRewards(address[] _initialValidators) external {
validatorList = _initialValidators;
}

function setToken(address _token) external {
token = _token;
}

function addBridgeTokenFeeReceivers(uint256 _amount) external {
address[] memory receivers = new address[](validatorList.length);
uint256[] memory rewards = new uint256[](validatorList.length);
feeAmount = _amount;
uint256 feePerValidator = _amount.div(validatorList.length);

uint256 randomValidatorIndex;
uint256 diff = _amount.sub(feePerValidator.mul(validatorList.length));
if (diff > 0) {
randomValidatorIndex = random(validatorList.length);
}

for (uint256 i = 0; i < validatorList.length; i++) {
uint256 feeToDistribute = feePerValidator;
if (diff > 0 && randomValidatorIndex == i) {
feeToDistribute = feeToDistribute.add(diff);
}
receivers[i] = validatorList[i];
rewards[i] = feeToDistribute;
}

require(token.call(abi.encodeWithSignature("mintReward(address[],uint256[])", receivers, rewards)));
}

function random(uint256 _count) public view returns(uint256) {
return uint256(blockhash(block.number.sub(1))) % _count;
}
}
49 changes: 2 additions & 47 deletions contracts/upgradeable_contracts/BaseFeeManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ import "./FeeTypes.sol";
contract BaseFeeManager is EternalStorage, FeeTypes {
using SafeMath for uint256;

bytes32 public constant REWARD_FOR_TRANSFERRING_FROM_HOME = keccak256(abi.encodePacked("reward-transferring-from-home"));

bytes32 public constant REWARD_FOR_TRANSFERRING_FROM_FOREIGN = keccak256(abi.encodePacked("reward-transferring-from-foreign"));

event HomeFeeUpdated(uint256 fee);
event ForeignFeeUpdated(uint256 fee);

Expand Down Expand Up @@ -43,54 +39,13 @@ contract BaseFeeManager is EternalStorage, FeeTypes {
return uintStorage[keccak256(abi.encodePacked("foreignFee"))];
}

function distributeFeeFromAffirmation(uint256 _fee) external {
distributeFeeProportionally(_fee, REWARD_FOR_TRANSFERRING_FROM_FOREIGN);
}
function distributeFeeFromAffirmation(uint256 _fee) external;

function distributeFeeFromSignatures(uint256 _fee) external {
distributeFeeProportionally(_fee, REWARD_FOR_TRANSFERRING_FROM_HOME);
}
function distributeFeeFromSignatures(uint256 _fee) external;

function getFeeManagerMode() public pure returns(bytes4);

function random(uint256 _count) public view returns(uint256) {
return uint256(blockhash(block.number.sub(1))) % _count;
}

function distributeFeeProportionally(uint256 _fee, bytes32 _direction) internal {
IRewardableValidators validators = rewardableValidatorContract();
address[] memory validatorList = validators.validatorList();
uint256 feePerValidator = _fee.div(validatorList.length);

uint256 randomValidatorIndex;
uint256 diff = _fee.sub(feePerValidator.mul(validatorList.length));
if (diff > 0) {
randomValidatorIndex = random(validatorList.length);
}

for (uint256 i = 0; i < validatorList.length; i++) {
uint256 feeToDistribute = feePerValidator;
if (diff > 0 && randomValidatorIndex == i) {
feeToDistribute = feeToDistribute.add(diff);
}
address rewardAddress = validators.getValidatorRewardAddress(validatorList[i]);
onFeeDistribution(rewardAddress, feeToDistribute, _direction);
}
}

function onFeeDistribution(address _rewardAddress, uint256 _fee, bytes32 _direction) internal {
if (_direction == REWARD_FOR_TRANSFERRING_FROM_FOREIGN) {
onAffirmationFeeDistribution(_rewardAddress, _fee);
} else {
onSignatureFeeDistribution(_rewardAddress, _fee);
}
}

function onAffirmationFeeDistribution(address _rewardAddress, uint256 _fee) internal;

function onSignatureFeeDistribution(address _rewardAddress, uint256 _fee) internal;

function rewardableValidatorContract() public view returns(IRewardableValidators) {
return IRewardableValidators(addressStorage[keccak256(abi.encodePacked("validatorContract"))]);
}
}
24 changes: 24 additions & 0 deletions contracts/upgradeable_contracts/BlockRewardFeeManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
pragma solidity 0.4.24;

import "./BaseFeeManager.sol";
import "../IBlockReward.sol";

contract BlockRewardFeeManager is BaseFeeManager {

function distributeFeeFromAffirmation(uint256 _fee) external {
distributeFeeFromBlockReward(_fee);
}

function distributeFeeFromSignatures(uint256 _fee) external {
distributeFeeFromBlockReward(_fee);
}

function distributeFeeFromBlockReward(uint256 _fee) internal {
IBlockReward blockReward = _blockRewardContract();
blockReward.addBridgeTokenFeeReceivers(_fee);
}

function _blockRewardContract() internal view returns(IBlockReward) {
return IBlockReward(addressStorage[keccak256(abi.encodePacked("blockRewardContract"))]);
}
}
56 changes: 56 additions & 0 deletions contracts/upgradeable_contracts/ValidatorsFeeManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
pragma solidity 0.4.24;

import "./BaseFeeManager.sol";
import "../IRewardableValidators.sol";

contract ValidatorsFeeManager is BaseFeeManager {

bytes32 public constant REWARD_FOR_TRANSFERRING_FROM_HOME = keccak256(abi.encodePacked("reward-transferring-from-home"));

bytes32 public constant REWARD_FOR_TRANSFERRING_FROM_FOREIGN = keccak256(abi.encodePacked("reward-transferring-from-foreign"));

function distributeFeeFromAffirmation(uint256 _fee) external {
distributeFeeProportionally(_fee, REWARD_FOR_TRANSFERRING_FROM_FOREIGN);
}

function distributeFeeFromSignatures(uint256 _fee) external {
distributeFeeProportionally(_fee, REWARD_FOR_TRANSFERRING_FROM_HOME);
}

function rewardableValidatorContract() internal view returns(IRewardableValidators) {
return IRewardableValidators(addressStorage[keccak256(abi.encodePacked("validatorContract"))]);
}

function distributeFeeProportionally(uint256 _fee, bytes32 _direction) internal {
IRewardableValidators validators = rewardableValidatorContract();
address[] memory validatorList = validators.validatorList();
uint256 feePerValidator = _fee.div(validatorList.length);

uint256 randomValidatorIndex;
uint256 diff = _fee.sub(feePerValidator.mul(validatorList.length));
if (diff > 0) {
randomValidatorIndex = random(validatorList.length);
}

for (uint256 i = 0; i < validatorList.length; i++) {
uint256 feeToDistribute = feePerValidator;
if (diff > 0 && randomValidatorIndex == i) {
feeToDistribute = feeToDistribute.add(diff);
}
address rewardAddress = validators.getValidatorRewardAddress(validatorList[i]);
onFeeDistribution(rewardAddress, feeToDistribute, _direction);
}
}

function onFeeDistribution(address _rewardAddress, uint256 _fee, bytes32 _direction) internal {
if (_direction == REWARD_FOR_TRANSFERRING_FROM_FOREIGN) {
onAffirmationFeeDistribution(_rewardAddress, _fee);
} else {
onSignatureFeeDistribution(_rewardAddress, _fee);
}
}

function onAffirmationFeeDistribution(address _rewardAddress, uint256 _fee) internal;

function onSignatureFeeDistribution(address _rewardAddress, uint256 _fee) internal;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pragma solidity 0.4.24;

import "../BlockRewardFeeManager.sol";

contract FeeManagerErcToErcPOSDAO is BlockRewardFeeManager {

function getFeeManagerMode() public pure returns(bytes4) {
return bytes4(keccak256(abi.encodePacked("manages-both-directions")));
}
}
122 changes: 116 additions & 6 deletions contracts/upgradeable_contracts/erc20_to_erc20/HomeBridgeErcToErc.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import "../../ERC677Receiver.sol";
import "../BasicHomeBridge.sol";
import "../ERC677Bridge.sol";
import "../OverdrawManagement.sol";
import "./RewardableHomeBridgeErcToErc.sol";
import "../../IBlockReward.sol";


contract HomeBridgeErcToErc is ERC677Receiver, EternalStorage, BasicBridge, BasicHomeBridge, ERC677Bridge, OverdrawManagement {
contract HomeBridgeErcToErc is ERC677Receiver, EternalStorage, BasicBridge, BasicHomeBridge, ERC677Bridge, OverdrawManagement, RewardableHomeBridgeErcToErc {

event AmountLimitExceeded(address recipient, uint256 value, bytes32 transactionHash);
event FeeDistributedFromAffirmation(uint256 feeAmount, bytes32 indexed transactionHash);
event FeeDistributedFromSignatures(uint256 feeAmount, bytes32 indexed transactionHash);

function initialize (
address _validatorContract,
Expand All @@ -27,6 +31,78 @@ contract HomeBridgeErcToErc is ERC677Receiver, EternalStorage, BasicBridge, Basi
address _owner
) public
returns(bool)
{
_initialize (
_validatorContract,
_dailyLimit,
_maxPerTx,
_minPerTx,
_homeGasPrice,
_requiredBlockConfirmations,
_erc677token,
_foreignDailyLimit,
_foreignMaxPerTx,
_owner
);
setInitialize(true);

return isInitialized();
}

function rewardableInitialize (
address _validatorContract,
uint256 _dailyLimit,
uint256 _maxPerTx,
uint256 _minPerTx,
uint256 _homeGasPrice,
uint256 _requiredBlockConfirmations,
address _erc677token,
uint256 _foreignDailyLimit,
uint256 _foreignMaxPerTx,
address _owner,
address _blockReward,
Copy link
Collaborator

Choose a reason for hiding this comment

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

in common case the erc20-to-erc20 bridge mode does not require the block reward contract. Reward could be assigned directly by the fee manager. So, I think, we need to have a separate contract like POSDAOHomeBridgeErcToErc which will extend HomeBridgeErcToErc and redefine rewardableInitialize() method with another set of parameters, this contract will also contain blockRewardContract() and setBlockRewardContract().

Another idea is that blockRewardContract and setBlockRewardContract should be implemented in the fee manager since they are related to the fee distribution functionality. In this case the bridge contract should proxy calls to the fee manager contract.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added POSDAOHomeBridgeErcToErc here 8ab76b9.

Another idea is that blockRewardContract and setBlockRewardContract should be implemented in the fee manager since they are related to the fee distribution functionality. In this case the bridge contract should proxy calls to the fee manager contract.

I implemented this approach, it makes sense to manage blockReward logic on fee manager since it is related to this contract functionality.

address _feeManager,
uint256 _homeFee,
uint256 _foreignFee
) public
returns(bool)
{
_initialize (
_validatorContract,
_dailyLimit,
_maxPerTx,
_minPerTx,
_homeGasPrice,
_requiredBlockConfirmations,
_erc677token,
_foreignDailyLimit,
_foreignMaxPerTx,
_owner
);
require(isContract(_feeManager));
require(_blockReward == address(0) || isContract(_blockReward));
addressStorage[keccak256(abi.encodePacked("feeManagerContract"))] = _feeManager;
_setFee(_feeManager, _homeFee, HOME_FEE);
_setFee(_feeManager, _foreignFee, FOREIGN_FEE);
addressStorage[keccak256(abi.encodePacked("blockRewardContract"))] = _blockReward;
setInitialize(true);

return isInitialized();
}

function _initialize (
address _validatorContract,
uint256 _dailyLimit,
uint256 _maxPerTx,
uint256 _minPerTx,
uint256 _homeGasPrice,
uint256 _requiredBlockConfirmations,
address _erc677token,
uint256 _foreignDailyLimit,
uint256 _foreignMaxPerTx,
address _owner
) internal
returns(bool)
{
require(!isInitialized());
require(_validatorContract != address(0) && isContract(_validatorContract));
Expand All @@ -45,10 +121,7 @@ contract HomeBridgeErcToErc is ERC677Receiver, EternalStorage, BasicBridge, Basi
uintStorage[keccak256(abi.encodePacked("executionDailyLimit"))] = _foreignDailyLimit;
uintStorage[keccak256(abi.encodePacked("executionMaxPerTx"))] = _foreignMaxPerTx;
setOwner(_owner);
setInitialize(true);
setErc677token(_erc677token);

return isInitialized();
}

function getBridgeMode() public pure returns(bytes4 _data) {
Expand All @@ -59,13 +132,50 @@ contract HomeBridgeErcToErc is ERC677Receiver, EternalStorage, BasicBridge, Basi
revert();
}

function blockRewardContract() public view returns(IBlockReward) {
return IBlockReward(addressStorage[keccak256(abi.encodePacked("blockRewardContract"))]);
}

function setBlockRewardContract(address _blockReward) public onlyOwner {
require(_blockReward != address(0) && isContract(_blockReward) && (IBlockReward(_blockReward).bridgesAllowedLength() != 0));
addressStorage[keccak256(abi.encodePacked("blockRewardContract"))] = _blockReward;
}

function onExecuteAffirmation(address _recipient, uint256 _value, bytes32 txHash) internal returns(bool) {
setTotalExecutedPerDay(getCurrentDay(), totalExecutedPerDay(getCurrentDay()).add(_value));
return erc677token().mint(_recipient, _value);
uint256 valueToMint = _value;
address feeManager = feeManagerContract();
if (feeManager != address(0)) {
uint256 fee = calculateFee(valueToMint, false, feeManager, FOREIGN_FEE);
distributeFeeFromAffirmation(fee, feeManager);
emit FeeDistributedFromAffirmation(fee, txHash);
Copy link
Collaborator

Choose a reason for hiding this comment

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

We don't need these event anymore after #176

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! Updated logic here 6b25ffb

valueToMint = valueToMint.sub(fee);
}
return erc677token().mint(_recipient, valueToMint);
}

function fireEventOnTokenTransfer(address _from, uint256 _value) internal {
emit UserRequestForSignature(_from, _value);
uint256 valueToTransfer = _value;
address feeManager = feeManagerContract();
if (feeManager != address(0)) {
uint256 fee = calculateFee(valueToTransfer, false, feeManager, HOME_FEE);
valueToTransfer = valueToTransfer.sub(fee);
}
emit UserRequestForSignature(_from, valueToTransfer);
}

function onSignaturesCollected(bytes _message) internal {
address feeManager = feeManagerContract();
if (feeManager != address(0)) {
address recipient;
uint256 amount;
bytes32 txHash;
address contractAddress;
(recipient, amount, txHash, contractAddress) = Message.parseMessage(_message);
uint256 fee = calculateFee(amount, true, feeManager, HOME_FEE);
distributeFeeFromSignatures(fee, feeManager);
emit FeeDistributedFromSignatures(fee, txHash);
Copy link
Collaborator

Choose a reason for hiding this comment

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

We don't need these event anymore after #176

}
}

function affirmationWithinLimits(uint256 _amount) internal view returns(bool) {
Expand Down
Loading