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 native to erc20 mediator on top of AMB #367

Merged
merged 35 commits into from
May 16, 2020
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
8d8dba4
Extract basic amb mediators functionality
patitonar Jan 31, 2020
6bfb232
Add amb-native-to-erc mediators
patitonar Feb 4, 2020
a1bbc5f
Add amb-native-to-erc fee manager
patitonar Feb 7, 2020
0ef329e
Add executeActionOnFixedTokens methods
patitonar Feb 11, 2020
6ef0ae5
Add HomeAMBNativeToErc20 unit tests
patitonar Feb 11, 2020
f5d9027
Add rewardable HomeAMBNativeToErc20 unit tests
patitonar Feb 12, 2020
339f82c
Add basic ForeignAMBNativeToErc20 unit tests
patitonar Feb 12, 2020
466810a
Add main ForeignAMBNativeToErc20 unit tests
patitonar Feb 13, 2020
da90634
Update flatten script
patitonar Feb 13, 2020
cec5150
Fix verifier
patitonar Feb 14, 2020
b56d95f
Add amb-native-to-erc deploy scripts
patitonar Feb 14, 2020
a3d56a3
lint fixes
patitonar Feb 14, 2020
eff5df1
Add amb-native-to-erc docs
patitonar Feb 17, 2020
8cc1e65
Add ClassicHomeAMBNativeToErc20 to be used with classic proxy
patitonar Feb 18, 2020
4a615de
Store mediator nonce in uintStorage
patitonar Feb 21, 2020
529d193
Revert "Add ClassicHomeAMBNativeToErc20 to be used with classic proxy"
patitonar Feb 26, 2020
88b992c
Use fee manager as external contract
patitonar Mar 19, 2020
414c782
Merge branch 'develop' into 365-amb-native-to-erc
patitonar Mar 19, 2020
661b781
Extract safe native transfer functionality into library
patitonar Mar 19, 2020
0c68dcf
linter fixes
patitonar Mar 19, 2020
5fbc11e
lint fixes
patitonar Mar 19, 2020
0da48b3
add safeSendValue unit tests
patitonar Mar 20, 2020
652982f
Merge branch 'develop' into 365-amb-native-to-erc
patitonar Mar 30, 2020
aa0cd59
Avoid mediator contract as reward receiver
patitonar Mar 30, 2020
a183203
Block multiple requests to mediator on fee distribution
patitonar Apr 1, 2020
53ecb56
fix comment
patitonar Apr 1, 2020
d8b8379
simplify MediatorMessagesGuard
patitonar Apr 2, 2020
0c7afe8
fix modifier
patitonar Apr 2, 2020
0dfe77d
Add method to fix mediator balance
patitonar Apr 2, 2020
9efb764
Merge branch 'develop' into 365-amb-native-to-erc
patitonar Apr 15, 2020
9af110e
Lint fixes
patitonar Apr 15, 2020
6e611e6
Add TokensBridged event
patitonar Apr 16, 2020
69a612e
Update messages control implementation
patitonar Apr 17, 2020
8b1172b
Merge branch 'develop' into 365-amb-native-to-erc
patitonar Apr 21, 2020
3c7a0fc
Fix alternative receiver behaviour for failed messages
patitonar Apr 21, 2020
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: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![Coverage Status](https://coveralls.io/repos/github/poanetwork/tokenbridge-contracts/badge.svg?branch=master)](https://coveralls.io/github/poanetwork/tokenbridge-contracts?branch=master)

# POA Bridge Smart Contracts
These contracts provide the core functionality for the POA bridge. They implement the logic to relay assests between two EVM-based blockchain networks. The contracts collect bridge validator's signatures to approve and facilitate relay operations.
These contracts provide the core functionality for the POA bridge. They implement the logic to relay assests between two EVM-based blockchain networks. The contracts collect bridge validator's signatures to approve and facilitate relay operations.

The POA bridge smart contracts are intended to work with [the bridge process implemented on NodeJS](https://github.com/poanetwork/token-bridge).
Please refer to the bridge process documentation to configure and deploy the bridge.
Expand Down Expand Up @@ -36,6 +36,7 @@ The POA bridge contracts consist of several components:
* The **Foreign Bridge** smart contract. This is deployed in the Ethereum Mainnet.
* Depending on the type of relay operations the following components are also used:
* in `NATIVE-TO-ERC` mode: the ERC20 token (in fact, the ERC677 extension is used) is deployed on the Foreign network;
* in `AMB-NATIVE-TO-ERC` mode: the ERC20 token (in fact, the ERC677 extension is used) is deployed on the Foreign network;
* in `ERC-TO-ERC` mode: the ERC20 token (in fact, the ERC677 extension is used) is deployed on the Home network;
* in `AMB-ERC-TO-ERC` mode: the ERC20 token (in fact, the ERC677 extension is used) is deployed on the Home network;
* in `ERC-TO-NATIVE` mode: The home network nodes must support consensus engine that allows using a smart contract for block reward calculation;
Expand All @@ -59,16 +60,17 @@ Responsibilities and roles of the bridge:
- **User** role:
- sends assets to Bridge contracts:
- in `NATIVE-TO-ERC` mode: send native coins to the Home Bridge to receive ERC20 tokens from the Foreign Bridge, send ERC20 tokens to the Foreign Bridge to unlock native coins from the Home Bridge;
- in `ERC-TO-ERC` mode: transfer ERC20 tokens to the Foreign Bridge to mint ERC20 tokens on the Home Network, transfer ERC20 tokens to the Home Bridge to unlock ERC20 tokens on Foreign networks;
- in `ERC-TO-ERC` mode: transfer ERC20 tokens to the Foreign Bridge to mint ERC20 tokens on the Home Network, transfer ERC20 tokens to the Home Bridge to unlock ERC20 tokens on Foreign networks;
- in `ERC-TO-NATIVE` mode: send ERC20 tokens to the Foreign Bridge to receive native coins from the Home Bridge, send native coins to the Home Bridge to unlock ERC20 tokens from the Foreign Bridge;
- in `ARBITRARY-MESSAGE` mode: Invoke Home/Foreign Bridge to send a message that will be executed on the other Network as an arbitrary contract method invocation;
- in `AMB-ERC-TO-ERC` mode: transfer ERC20 tokens to the Foreign Mediator which will interact with Foreign AMB Bridge to mint ERC20 tokens on the Home Network, transfer ERC20 tokens to the Home Mediator which will interact with Home AMB Bridge to unlock ERC20 tokens on Foreign network.
- in `AMB-NATIVE-TO-ERC` mode: send native coins to the Home Mediator which will interact with Home AMB Bridge to mint ERC20 tokens on the Foreign Network, transfer ERC20 tokens to the Foreign Mediator which will interact with Foreign AMB Bridge to unlock native coins from Home network.

## Usage

There are two ways to deploy contracts:
* install and use NodeJS
* use Docker to deploy
* use Docker to deploy

### Deployment with NodeJS

Expand Down Expand Up @@ -101,7 +103,7 @@ npm run flatten
The flattened contracts can be found in the `flats` directory.

### Deployment in the Docker environment
[Docker](https://www.docker.com/community-edition) and [Docker Compose](https://docs.docker.com/compose/install/) can be used to deploy contracts without NodeJS installed on the system.
[Docker](https://www.docker.com/community-edition) and [Docker Compose](https://docs.docker.com/compose/install/) can be used to deploy contracts without NodeJS installed on the system.
If you are on Linux, we recommend you [create a docker group and add your user to it](https://docs.docker.com/install/linux/linux-postinstall/), so that you can use the CLI without `sudo`.

#### Prepare the docker container
Expand Down
15 changes: 15 additions & 0 deletions REWARD_MANAGEMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,18 @@ Fees are calculated and distributed on Home network. Validators will receive ERC
Fees are calculated and distributed on Home network. Validators will receive ERC20 tokens.
![ERC-ERC-HomeToForeign](https://user-images.githubusercontent.com/4614574/59939670-0cc32480-942f-11e9-9693-727125555c97.png)

## AMB-NATIVE-TO-ERC
Configuration:
```
HOME_REWARDABLE=ONE_DIRECTION
FOREIGN_REWARDABLE=ONE_DIRECTION
```
### Home to Foreign transfer
Fees are calculated and distributed on Foreign network. The reward accounts will receive ERC20 tokens.
![AMB-NATIVE-TO-ERC677-Home-Foreign](https://user-images.githubusercontent.com/4614574/74660965-dd0f1c80-5175-11ea-8d6c-51b8bd85f844.png)

### Foreign to Home transfer
Fees are calculated and distributed on Home network. The reward accounts will receive native tokens.
![AMB-NATIVE-TO-ERC677-Foreign-Home](https://user-images.githubusercontent.com/4614574/74660986-e6988480-5175-11ea-9216-7f008a6fdaf0.png)


237 changes: 237 additions & 0 deletions contracts/upgradeable_contracts/BaseMediatorFeeManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
pragma solidity 0.4.24;

import "../upgradeability/EternalStorage.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";

/**
* @title BaseMediatorFeeManager
* @dev Common functionality of fee managers for AMB mediators to store, calculate and perform actions related to
* fee distribution. The fee manager is used as a logic contract only, the state variables are stored in the mediator
* contract, so methods should be invoked by using delegatecall or callcode.
*/
contract BaseMediatorFeeManager is EternalStorage {
using SafeMath for uint256;

event FeeUpdated(uint256 fee);

// This is not a real fee value but a relative value used to calculate the fee percentage.
// 1 ether = 100% of the value.
uint256 internal constant MAX_FEE = 1 ether;
bytes32 internal constant FEE_STORAGE_KEY = 0x833b9f6abf0b529613680afe2a00fa663cc95cbdc47d726d85a044462eabbf02; // keccak256(abi.encodePacked("fee"))
bytes32 internal constant REWARD_ACCOUNTS_COUNT = 0x7a0619a0d97f7e9c83d1a6c44be033aecca1245b900bb0e97a7dcaae387eb874; // keccak256(abi.encodePacked("rewardAccountCount"))
address internal constant F_ADDR = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF;
uint256 internal constant MAX_REWARD_ACCOUNTS = 50;

modifier validFee(uint256 _fee) {
require(_fee < MAX_FEE);
/* solcov ignore next */
_;
}

/**
* @dev Initialize the list of accounts that receives rewards for the mediator operations.
* The list of accounts is stored as a Linked List where the list starts and ends at F_ADDR.
* Example: F_ADDR -> account1; account1 -> account2; account2 -> F_ADDR
* @param _accounts list of accounts
*/
function initializeRewardAccounts(address[] _accounts) public {
require(_accounts.length > 0);
for (uint256 i = 0; i < _accounts.length; i++) {
require(_accounts[i] != address(0) && _accounts[i] != F_ADDR);
require(!isRewardAccount(_accounts[i]));

if (i == 0) {
setNextRewardAccount(F_ADDR, _accounts[i]);
if (_accounts.length == 1) {
setNextRewardAccount(_accounts[i], F_ADDR);
}
} else if (i == _accounts.length - 1) {
setNextRewardAccount(_accounts[i - 1], _accounts[i]);
setNextRewardAccount(_accounts[i], F_ADDR);
} else {
setNextRewardAccount(_accounts[i - 1], _accounts[i]);
}
}

setRewardAccountsCount(_accounts.length);
}

/**
* @dev Tells the list of accounts that receives rewards for the operations.
* @return the list of reward accounts
*/
function rewardAccounts() external view returns (address[] memory) {
address[] memory list = new address[](rewardAccountsCount());
uint256 counter = 0;
address nextRewardAccount = getNextRewardAccount(F_ADDR);
require(nextRewardAccount != address(0));

while (nextRewardAccount != F_ADDR) {
list[counter] = nextRewardAccount;
nextRewardAccount = getNextRewardAccount(nextRewardAccount);
counter++;

require(nextRewardAccount != address(0));
}

return list;
}

/**
* @dev Calculates the fee amount to be subtracted from the value.
* @param _value the base value from which fees are calculated
*/
function calculateFee(uint256 _value) external view returns (uint256) {
uint256 fee = getFee();
return _value.mul(fee).div(MAX_FEE);
}

/**
* @dev Sets the fee percentage amount for the mediator operations.
* @param _fee the fee percentage
*/
function setFee(uint256 _fee) external validFee(_fee) {
uintStorage[FEE_STORAGE_KEY] = _fee;
emit FeeUpdated(_fee);
}

/**
* @dev Tells the fee percentage amount for the mediator operations.
* @return the fee percentage amount
*/
function getFee() public view returns (uint256) {
return uintStorage[FEE_STORAGE_KEY];
}

/**
* @dev Adds a new account to the list of accounts to receive rewards for the operations.
* @param _account new reward account
*/
function addRewardAccount(address _account) external {
require(_account != address(0) && _account != F_ADDR);
require(!isRewardAccount(_account));

address firstAccount = getNextRewardAccount(F_ADDR);
// if list wasn't initialized
if (firstAccount == address(0)) {
firstAccount = F_ADDR;
}
setNextRewardAccount(_account, firstAccount);
setNextRewardAccount(F_ADDR, _account);
setRewardAccountsCount(rewardAccountsCount().add(1));
}

/**
* @dev Removes an account from the list of accounts to receive rewards for the operations.
* @param _account to be removed
*/
function removeRewardAccount(address _account) external {
require(isRewardAccount(_account));
address accountNext = getNextRewardAccount(_account);
address index = F_ADDR;
address next = getNextRewardAccount(index);
require(next != address(0));

while (next != _account) {
index = next;
next = getNextRewardAccount(index);

require(next != F_ADDR && next != address(0));
}

setNextRewardAccount(index, accountNext);
delete addressStorage[keccak256(abi.encodePacked("rewardAccountList", _account))];
setRewardAccountsCount(rewardAccountsCount().sub(1));
}

/**
* @dev Tells the amount of accounts in the list of reward accounts.
* @return amount of accounts.
*/
function rewardAccountsCount() internal view returns (uint256) {
return uintStorage[REWARD_ACCOUNTS_COUNT];
}

/**
* @dev Stores the amount of accounts in the list of reward accounts.
* @param _count amount of accounts.
*/
function setRewardAccountsCount(uint256 _count) internal {
require(_count <= MAX_REWARD_ACCOUNTS);
uintStorage[REWARD_ACCOUNTS_COUNT] = _count;
}

/**
* @dev Tells if the account is part of the list of reward accounts.
* @param _account to check if is part of the list.
* @return true if the account is in the list
*/
function isRewardAccount(address _account) internal view returns (bool) {
return _account != F_ADDR && getNextRewardAccount(_account) != address(0);
}

/**
* @dev Tells the next account in the list of reward accounts.
* @param _address previous account in the list.
* @return _account next account in the list.
*/
function getNextRewardAccount(address _address) internal view returns (address) {
return addressStorage[keccak256(abi.encodePacked("rewardAccountList", _address))];
}

/**
* @dev Stores the next account in the list of reward accounts.
* @param _prevAccount previous account in the list.
* @param _account next account in the list.
*/
function setNextRewardAccount(address _prevAccount, address _account) internal {
addressStorage[keccak256(abi.encodePacked("rewardAccountList", _prevAccount))] = _account;
}

/**
* @dev Distributes the provided amount of fees proportionally to the list of reward accounts.
* In case the fees cannot be equally distributed, the remaining difference will be distributed to an account
* in a semi-random way.
* @param _fee total amount to be distributed to the list of reward accounts.
*/
function distributeFee(uint256 _fee) external {
uint256 numOfAccounts = rewardAccountsCount();
if (numOfAccounts > 0) {
uint256 feePerValidator = _fee.div(numOfAccounts);
uint256 randomValidatorIndex;
uint256 diff = _fee.sub(feePerValidator.mul(numOfAccounts));
if (diff > 0) {
randomValidatorIndex = random(numOfAccounts);
}

address nextRewardAccount = getNextRewardAccount(F_ADDR);
require((nextRewardAccount != F_ADDR) && (nextRewardAccount != address(0)));

uint256 i = 0;
while (nextRewardAccount != F_ADDR) {
uint256 feeToDistribute = feePerValidator;
if (diff > 0 && randomValidatorIndex == i) {
feeToDistribute = feeToDistribute.add(diff);
}

onFeeDistribution(nextRewardAccount, feeToDistribute);

nextRewardAccount = getNextRewardAccount(nextRewardAccount);
require(nextRewardAccount != address(0));
i = i + 1;
}
}
}

/**
* @dev Calculates a random number based on the block number.
* @param _count the max value for the random number.
* @return a number between 0 and _count.
*/
function random(uint256 _count) internal view returns (uint256) {
return uint256(blockhash(block.number.sub(1))) % _count;
}

/* solcov ignore next */
function onFeeDistribution(address _rewardAddress, uint256 _fee) internal;
}
Loading