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

Helper contract for token-subsidized claims #76

Draft
wants to merge 1 commit into
base: develop
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
154 changes: 154 additions & 0 deletions contracts/helpers/OmnibridgeSubsidizeHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
pragma solidity 0.7.5;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "../interfaces/IOmnibridge.sol";
import "../interfaces/IERC677.sol";
import "../libraries/AddressHelper.sol";
import "../libraries/Bytes.sol";
import "../upgradeable_contracts/modules/OwnableModule.sol";
import "../upgradeable_contracts/modules/fee_manager/OmnibridgeFeeManager.sol";
import "../upgradeable_contracts/Claimable.sol";

/**
* @title OmnibridgeSubsidizeHelper
* @dev Intermediary helper contract for taking extra fees for automatic claim.
*/
contract OmnibridgeSubsidizeHelper is IOmnibridge, OwnableModule, Claimable {
using SafeERC20 for IERC677;
using SafeERC20 for IERC20;
using SafeMath for uint256;

IOmnibridge public immutable bridge;
OmnibridgeFeeManager public feeManager;

mapping(address => bool) public enabledTokens;

bytes32 public constant HOME_TO_FOREIGN_FEE = 0x741ede137d0537e88e0ea0ff25b1f22d837903dbbee8980b4a06e8523247ee26; // keccak256(abi.encodePacked("homeToForeignFee"))

/**
* @dev Initializes this contract.
* @param _bridge address of the HomeOmnibridge/ForeignOmnibridge contract.
* @param _owner address of the contract owner.
*/
constructor(IOmnibridge _bridge, address _owner) OwnableModule(_owner) {
bridge = _bridge;
}

/**
* @dev Sets the fee manager address.
* @param _feeManager address of the OmnibridgeFeeManager contract.
*/
function setFeeManager(OmnibridgeFeeManager _feeManager) external onlyOwner {
feeManager = _feeManager;
}

/**
* @dev Enables/disables automatic claim for some particular token contract address.
* @param _token address of the token contract.
* @param _enabled true to enable automatic claim.
*/
function enableToken(address _token, bool _enabled) external onlyOwner {
enabledTokens[_token] = _enabled;
}

/**
* @dev ERC677 transfer callback function.
* Subtracts a configured fee and bridges the remaining amount to the regular OB.
* @param _from address of tokens sender.
* @param _value amount of transferred tokens.
* @param _data additional transfer data, can be used for passing alternative receiver address.
*/
function onTokenTransfer(
address _from,
uint256 _value,
bytes memory _data
) external returns (bool) {
uint256 valueToBridge = _distributeFee(msg.sender, _from, _value);
IERC677(msg.sender).transferAndCall(address(bridge), valueToBridge, _data);

return true;
}

/**
* @dev Initiate the bridge operation for some amount of tokens from msg.sender to msg.sender on the other side.
* The user should first call Approve method of the ERC677 token.
* Subtracts a configured fee and bridges the remaining amount to the regular OB.
* @param _token bridged token contract address.
* @param _value amount of tokens to be transferred to the other network.
*/
function relayTokens(address _token, uint256 _value) external override {
relayTokens(_token, msg.sender, _value);
}

/**
* @dev Initiate the bridge operation for some amount of tokens from msg.sender.
* The user should first call Approve method of the ERC677 token.
* Subtracts a configured fee and bridges the remaining amount to the regular OB.
* @param _token bridged token contract address.
* @param _receiver address that will receive tokens on the other network.
* @param _value amount of tokens to be transferred to the other network.
*/
function relayTokens(
address _token,
address _receiver,
uint256 _value
) public override {
IERC20(_token).safeTransferFrom(msg.sender, address(this), _value);
uint256 valueToBridge = _distributeFee(_token, msg.sender, _value);
IERC20(_token).approve(address(bridge), valueToBridge);
bridge.relayTokens(_token, _receiver, valueToBridge);
}

/**
* @dev Initiate the bridge operation for some amount of tokens from msg.sender.
* The user should first call Approve method of the ERC677 token.
* Subtracts a configured fee and bridges the remaining amount to the regular OB.
* @param _token bridged token contract address.
* @param _receiver address that will receive the native tokens on the other network.
* @param _value amount of tokens to be transferred to the other network.
* @param _data additional transfer data to be used on the other side.
*/
function relayTokensAndCall(
address _token,
address _receiver,
uint256 _value,
bytes memory _data
) external override {
IERC20(_token).safeTransferFrom(msg.sender, address(this), _value);
uint256 valueToBridge = _distributeFee(_token, msg.sender, _value);
IERC20(_token).approve(address(bridge), valueToBridge);
bridge.relayTokensAndCall(_token, _receiver, valueToBridge, _data);
}

function _distributeFee(
address _token,
address _from,
uint256 _value
) internal returns (uint256) {
require(enabledTokens[_token]);

uint256 fee = feeManager.calculateFee(HOME_TO_FOREIGN_FEE, _token, _from, _value);
IERC677(_token).safeTransfer(address(feeManager), fee);
feeManager.distributeFee(_token);
return _value.sub(fee);
}

/**
* @dev Claims stuck coins/tokens.
* Only contract owner can call this method.
* @param _token address of claimed token contract, address(0) for native coins.
* @param _to address of tokens receiver
*/
function claimTokens(address _token, address _to) external onlyOwner {
claimValues(_token, _to);
}

function calculateFee(
address _token,
address _from,
uint256 _value
) external view returns (uint256) {
return feeManager.calculateFee(HOME_TO_FOREIGN_FEE, _token, _from, _value);
}
}
2 changes: 2 additions & 0 deletions contracts/interfaces/IOmnibridge.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
pragma solidity 0.7.5;

interface IOmnibridge {
function relayTokens(address _token, uint256 _value) external;

function relayTokens(
address _token,
address _receiver,
Expand Down
2 changes: 1 addition & 1 deletion contracts/upgradeable_contracts/HomeOmnibridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ contract HomeOmnibridge is
addTotalExecutedPerDay(_token, getCurrentDay(), _value);

uint256 valueToBridge = _value;
uint256 fee = _distributeFee(FOREIGN_TO_HOME_FEE, _isNative, address(0), _token, valueToBridge);
uint256 fee = _distributeFee(FOREIGN_TO_HOME_FEE, _isNative, _recipient, _token, valueToBridge);
bytes32 _messageId = messageId();
if (fee > 0) {
emit FeeDistributed(fee, _token, _messageId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
pragma solidity 0.7.5;
// solhint-disable-next-line compiler-version
pragma abicoder v2;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
Expand All @@ -19,32 +21,40 @@ contract OmnibridgeFeeManager is MediatorOwnableModule {
uint256 internal constant MAX_FEE = 1 ether;
uint256 internal constant MAX_REWARD_ACCOUNTS = 50;

address internal constant ANY_ADDRESS = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF;
bytes32 public constant HOME_TO_FOREIGN_FEE = 0x741ede137d0537e88e0ea0ff25b1f22d837903dbbee8980b4a06e8523247ee26; // keccak256(abi.encodePacked("homeToForeignFee"))
bytes32 public constant FOREIGN_TO_HOME_FEE = 0x03be2b2875cb41e0e77355e802a16769bb8dfcf825061cde185c73bf94f12625; // keccak256(abi.encodePacked("foreignToHomeFee"))

// mapping feeType => token address => fee percentage
mapping(bytes32 => mapping(address => uint256)) internal fees;
struct FeeParams {
uint256 percentage;
uint256 minFee;
uint256 maxFee;
}

// mapping feeType => token address => token sender => fee params
mapping(bytes32 => mapping(address => mapping(address => FeeParams))) internal fees;
address[] internal rewardAddresses;

event FeeUpdated(bytes32 feeType, address indexed token, uint256 fee);
event FeeUpdated(bytes32 indexed feeType, address indexed token, address sender, FeeParams fee);

/**
* @dev Stores the initial parameters of the fee manager.
* @param _mediator address of the mediator contract used together with this fee manager.
* @param _owner address of the contract owner.
* @param _rewardAddresses list of unique initial reward addresses, between whom fees will be distributed
* @param _fees array with initial fees for both bridge directions.
* [ 0 = homeToForeignFee, 1 = foreignToHomeFee ]
* @param _homeToForeignFees initial fee parameters for HOME_TO_FOREIGN direction.
* @param _foreignToHomeFees initial fee parameters for FOREIGN_TO_HOME direction.
*/
constructor(
address _mediator,
address _owner,
address[] memory _rewardAddresses,
uint256[2] memory _fees
FeeParams memory _homeToForeignFees,
FeeParams memory _foreignToHomeFees
) MediatorOwnableModule(_mediator, _owner) {
require(_rewardAddresses.length <= MAX_REWARD_ACCOUNTS);
_setFee(HOME_TO_FOREIGN_FEE, address(0), _fees[0]);
_setFee(FOREIGN_TO_HOME_FEE, address(0), _fees[1]);
_setFee(HOME_TO_FOREIGN_FEE, ANY_ADDRESS, ANY_ADDRESS, _homeToForeignFees);
_setFee(FOREIGN_TO_HOME_FEE, ANY_ADDRESS, ANY_ADDRESS, _foreignToHomeFees);

for (uint256 i = 0; i < _rewardAddresses.length; i++) {
require(_isValidAddress(_rewardAddresses[i]));
Expand All @@ -70,16 +80,7 @@ contract OmnibridgeFeeManager is MediatorOwnableModule {
uint64 patch
)
{
return (1, 0, 0);
}

/**
* @dev Throws if given fee amount is invalid.
*/
modifier validFee(uint256 _fee) {
require(_fee < MAX_FEE);
/* solcov ignore next */
_;
return (2, 0, 0);
}

/**
Expand All @@ -96,49 +97,68 @@ contract OmnibridgeFeeManager is MediatorOwnableModule {
* Only the owner can call this method.
* @param _feeType type of the updated fee, can be one of [HOME_TO_FOREIGN_FEE, FOREIGN_TO_HOME_FEE].
* @param _token address of the token contract for which fee should apply, 0x00..00 describes the initial fee for newly created tokens.
* @param _fee new fee value, in percentage (1 ether == 10**18 == 100%).
* @param _sender address of the specific tokens sender to apply fees for.
* @param _params new fee parameters.
*/
function setFee(
bytes32 _feeType,
address _token,
uint256 _fee
address _sender,
FeeParams memory _params
) external validFeeType(_feeType) onlyOwner {
_setFee(_feeType, _token, _fee);
_setFee(_feeType, _token, _sender, _params);
}

/**
* @dev Retrieves the value for the particular fee type.
* @param _feeType type of the updated fee, can be one of [HOME_TO_FOREIGN_FEE, FOREIGN_TO_HOME_FEE].
* @param _token address of the token contract for which fee should apply, 0x00..00 describes the initial fee for newly created tokens.
* @return fee value associated with the requested fee type.
* @param _sender address of the specific tokens sender to get fees for.
* @return fee parameters value associated with the specific fee type, token and sender addresses.
*/
function getFee(bytes32 _feeType, address _token) public view validFeeType(_feeType) returns (uint256) {
// use token-specific fee if one is registered
uint256 _tokenFee = fees[_feeType][_token];
if (_tokenFee > 0) {
return _tokenFee - 1;
function getFee(
bytes32 _feeType,
address _token,
address _sender
) public view validFeeType(_feeType) returns (FeeParams memory) {
FeeParams memory params = fees[_feeType][_token][_sender];
if (params.maxFee > 0) {
return params;
}
params = fees[_feeType][ANY_ADDRESS][_sender];
if (params.maxFee > 0) {
return params;
}
// use default fee otherwise
return fees[_feeType][address(0)] - 1;
params = fees[_feeType][_token][ANY_ADDRESS];
if (params.maxFee > 0) {
return params;
}
return fees[_feeType][ANY_ADDRESS][ANY_ADDRESS];
}

/**
* @dev Calculates the amount of fee to pay for the value of the particular fee type.
* @param _feeType type of the updated fee, can be one of [HOME_TO_FOREIGN_FEE, FOREIGN_TO_HOME_FEE].
* @param _token address of the token contract for which fee should apply, 0x00..00 describes the initial fee for newly created tokens.
* @param _sender address of the specific tokens sender to get fees for.
* @param _value bridged value, for which fee should be evaluated.
* @return amount of fee to be subtracted from the transferred value.
*/
function calculateFee(
bytes32 _feeType,
address _token,
address _sender,
uint256 _value
) public view returns (uint256) {
if (rewardAddresses.length == 0) {
return 0;
}
uint256 _fee = getFee(_feeType, _token);
return _value.mul(_fee).div(MAX_FEE);
FeeParams memory params = getFee(_feeType, _token, _sender);
uint256 proportionalFee = _value.mul(params.percentage).div(MAX_FEE);
return
proportionalFee > params.minFee
? (proportionalFee > params.maxFee ? params.maxFee : proportionalFee)
: params.minFee;
}

/**
Expand Down Expand Up @@ -240,15 +260,21 @@ contract OmnibridgeFeeManager is MediatorOwnableModule {
* @dev Internal function for updating the fee value for the given fee type.
* @param _feeType type of the updated fee, can be one of [HOME_TO_FOREIGN_FEE, FOREIGN_TO_HOME_FEE].
* @param _token address of the token contract for which fee should apply, 0x00..00 describes the initial fee for newly created tokens.
* @param _fee new fee value, in percentage (1 ether == 10**18 == 100%).
* @param _sender address of the specific tokens sender to set fees for.
* @param _params new fee parameters.
*/
function _setFee(
bytes32 _feeType,
address _token,
uint256 _fee
) internal validFee(_fee) {
fees[_feeType][_token] = 1 + _fee;
emit FeeUpdated(_feeType, _token, _fee);
address _sender,
FeeParams memory _params
) internal {
require(_params.percentage < MAX_FEE);
require(_params.minFee <= _params.maxFee);

fees[_feeType][_token][_sender] = _params;

emit FeeUpdated(_feeType, _token, _sender, _params);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ abstract contract OmnibridgeFeeManagerConnector is Ownable {
* @dev Internal function for calculating and distributing fee through the separate fee manager contract.
* @param _feeType type of the fee, can be one of [HOME_TO_FOREIGN_FEE, FOREIGN_TO_HOME_FEE].
* @param _isNative true, if distributed token is native to this side of the bridge.
* @param _from address of the tokens sender, needed only if _feeType is HOME_TO_FOREIGN_FEE.
* @param _from address of the tokens sender.
* @param _token address of the token contract, for which fee should be processed.
* @param _value amount of tokens bridged.
* @return total amount of fee distributed.
Expand All @@ -71,7 +71,7 @@ abstract contract OmnibridgeFeeManagerConnector is Ownable {
if (_feeType == HOME_TO_FOREIGN_FEE && manager.isRewardAddress(_from)) {
return 0;
}
uint256 fee = manager.calculateFee(_feeType, _token, _value);
uint256 fee = manager.calculateFee(_feeType, _token, _from, _value);
if (fee > 0) {
if (_feeType == HOME_TO_FOREIGN_FEE) {
// for home -> foreign direction, fee is collected using transfer(address,uint256) method
Expand Down
12 changes: 11 additions & 1 deletion deploy/src/omnibridge/home.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,19 @@ async function deployHome() {
HOME_TO_FOREIGN_FEE: ${homeFeeInWei} which is ${HOME_TRANSACTIONS_FEE * 100}%
FOREIGN_TO_HOME_FEE: ${foreignFeeInWei} which is ${FOREIGN_TRANSACTIONS_FEE * 100}%
`)
const homeFee = {
percentage: homeFeeInWei,
minFee: '0',
maxFee: toWei('100'),
}
const foreignFee = {
percentage: foreignFeeInWei,
minFee: '0',
maxFee: toWei('100'),
}
const manager = await deployContract(
OmnibridgeFeeManager,
[homeBridgeStorage.options.address, HOME_BRIDGE_OWNER, rewardList, [homeFeeInWei, foreignFeeInWei]],
[homeBridgeStorage.options.address, HOME_BRIDGE_OWNER, rewardList, homeFee, foreignFee],
{ nonce: nonce++ }
)
feeManager = manager.options.address
Expand Down
Loading