// File: contracts/interfaces/IERC20.sol
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.6.0 <0.8.0;
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the token decimals.
*/
function decimals() external view returns (uint8);
/**
* @dev Returns the token symbol.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the token name.
*/
function name() external view returns (string memory);
/**
* @dev Returns the bep token owner. (This is a BEP-20 token specific.)
*/
function getOwner() external view returns (address);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount)
external
returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address _owner, address spender)
external
view
returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}
// File: contracts/interfaces/IBurnableToken.sol
pragma solidity >=0.6.0 <0.8.0;
interface IBurnableToken is IERC20 {
function mint(address target, uint256 amount) external returns (bool);
function burn(uint256 amount) external returns (bool);
function mintable() external returns (bool);
}
// File: contracts/interfaces/ISwapContract.sol
pragma solidity >=0.6.0 <0.8.0;
interface ISwapContract {
function singleTransferERC20(
address _destToken,
address _to,
uint256 _amount,
uint256 _totalSwapped,
uint256 _rewardsAmount,
bytes32[] memory _redeemedFloatTxIds
) external returns (bool);
function multiTransferERC20TightlyPacked(
address _destToken,
bytes32[] memory _addressesAndAmounts,
uint256 _totalSwapped,
uint256 _rewardsAmount,
bytes32[] memory _redeemedFloatTxIds
) external returns (bool);
function collectSwapFeesForBTC(
address _destToken,
uint256 _incomingAmount,
uint256 _minerFee,
uint256 _rewardsAmount
) external returns (bool);
function recordIncomingFloat(
address _token,
bytes32 _addressesAndAmountOfFloat,
bool _zerofee,
bytes32 _txid
) external returns (bool);
function recordOutcomingFloat(
address _token,
bytes32 _addressesAndAmountOfLPtoken,
uint256 _minerFee,
bytes32 _txid
) external returns (bool);
function distributeNodeRewards() external returns (bool);
function recordUTXOSweepMinerFee(uint256 _minerFee, bytes32 _txid)
external
returns (bool);
function churn(
address _newOwner,
bytes32[] memory _rewardAddressAndAmounts,
bool[] memory _isRemoved,
uint8 _churnedInCount,
uint8 _tssThreshold,
uint8 _nodeRewardsRatio,
uint8 _withdrawalFeeBPS
) external returns (bool);
function isTxUsed(bytes32 _txid) external view returns (bool);
function getCurrentPriceLP() external view returns (uint256);
function getDepositFeeRate(address _token, uint256 _amountOfFloat)
external
view
returns (uint256);
function getFloatReserve(address _tokenA, address _tokenB)
external
returns (uint256 reserveA, uint256 reserveB);
function getActiveNodes() external returns (bytes32[] memory);
}
// File: @openzeppelin/contracts/GSN/Context.sol
pragma solidity >=0.6.0 <0.8.0;
/*
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address payable) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}
// File: @openzeppelin/contracts/access/Ownable.sol
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor () internal {
address msgSender = _msgSender();
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(_owner == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
// File: @openzeppelin/contracts/math/SafeMath.sol
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
// File: contracts/SwapContract.sol
pragma solidity >=0.6.0 <0.8.0;
contract SwapContract is Ownable, ISwapContract {
using SafeMath for uint256;
address public WBTC_ADDR;
address public lpToken;
mapping(address => bool) public whitelist;
uint8 public churnedInCount;
uint8 public tssThreshold;
uint8 public nodeRewardsRatio;
uint8 public depositFeesBPS;
uint8 public withdrawalFeeBPS;
uint256 public lockedLPTokensForNode;
uint256 public feesLPTokensForNode;
uint256 public initialExchangeRate;
uint256 private priceDecimals;
uint256 private lpDecimals;
mapping(address => uint256) private floatAmountOf;
mapping(bytes32 => bool) private used;
// Node lists
mapping(address => bytes32) private nodes;
mapping(address => bool) private isInList;
address[] private nodeAddrs;
/**
* Events
*/
event Swap(address from, address to, uint256 amount);
event RewardsCollection(
address feesToken,
uint256 rewards,
uint256 amountLPTokensForNode,
uint256 currentPriceLP
);
event IssueLPTokensForFloat(
address to,
uint256 amountOfFloat,
uint256 amountOfLP,
uint256 currentPriceLP,
uint256 depositFees,
bytes32 txid
);
event BurnLPTokensForFloat(
address token,
uint256 amountOfLP,
uint256 amountOfFloat,
uint256 currentPriceLP,
uint256 withdrawal,
bytes32 txid
);
modifier priceCheck() {
uint256 beforePrice = getCurrentPriceLP();
_;
require(getCurrentPriceLP() >= beforePrice, "Invalid LPT price");
}
constructor(
address _lpToken,
address _wbtc,
uint256 _existingBTCFloat
) public {
// Set lpToken address
lpToken = _lpToken;
// Set initial lpDecimals of LP token
lpDecimals = 10**IERC20(lpToken).decimals();
// Set WBTC address
WBTC_ADDR = _wbtc;
// Set nodeRewardsRatio
nodeRewardsRatio = 66;
// Set depositFeesBPS
depositFeesBPS = 50;
// Set withdrawalFeeBPS
withdrawalFeeBPS = 20;
// Set priceDecimals
priceDecimals = 10**8;
// Set initialExchangeRate
initialExchangeRate = priceDecimals;
// Set lockedLPTokensForNode
lockedLPTokensForNode = 0;
// Set feesLPTokensForNode
feesLPTokensForNode = 0;
// Set whitelist addresses
whitelist[WBTC_ADDR] = true;
whitelist[lpToken] = true;
whitelist[address(0)] = true;
floatAmountOf[address(0)] = _existingBTCFloat;
}
/**
* Transfer part
*/
/// @dev singleTransferERC20 sends tokens from contract.
/// @param _destToken The address of target token.
/// @param _to The address of recipient.
/// @param _amount The amount of tokens.
/// @param _totalSwapped The amount of swap.
/// @param _rewardsAmount The fees that should be paid.
/// @param _redeemedFloatTxIds The txids which is for recording.
function singleTransferERC20(
address _destToken,
address _to,
uint256 _amount,
uint256 _totalSwapped,
uint256 _rewardsAmount,
bytes32[] memory _redeemedFloatTxIds
) external override onlyOwner returns (bool) {
require(whitelist[_destToken], "_destToken is not whitelisted");
require(
_destToken != address(0),
"_destToken should not be address(0)"
);
address _feesToken = address(0);
if (_totalSwapped > 0) {
_swap(address(0), WBTC_ADDR, _totalSwapped);
} else if (_totalSwapped == 0) {
_feesToken = WBTC_ADDR;
}
if (_destToken == lpToken) {
_feesToken = lpToken;
}
_rewardsCollection(_feesToken, _rewardsAmount);
_addUsedTxs(_redeemedFloatTxIds);
require(IERC20(_destToken).transfer(_to, _amount));
return true;
}
/// @dev multiTransferERC20TightlyPacked sends tokens from contract.
/// @param _destToken The address of target token.
/// @param _addressesAndAmounts The address of recipient and amount.
/// @param _totalSwapped The amount of swap.
/// @param _rewardsAmount The fees that should be paid.
/// @param _redeemedFloatTxIds The txids which is for recording.
function multiTransferERC20TightlyPacked(
address _destToken,
bytes32[] memory _addressesAndAmounts,
uint256 _totalSwapped,
uint256 _rewardsAmount,
bytes32[] memory _redeemedFloatTxIds
) external override onlyOwner returns (bool) {
require(whitelist[_destToken], "_destToken is not whitelisted");
require(
_destToken != address(0),
"_destToken should not be address(0)"
);
address _feesToken = address(0);
if (_totalSwapped > 0) {
_swap(address(0), WBTC_ADDR, _totalSwapped);
} else if (_totalSwapped == 0) {
_feesToken = WBTC_ADDR;
}
if (_destToken == lpToken) {
_feesToken = lpToken;
}
_rewardsCollection(_feesToken, _rewardsAmount);
_addUsedTxs(_redeemedFloatTxIds);
for (uint256 i = 0; i < _addressesAndAmounts.length; i++) {
require(
IERC20(_destToken).transfer(
address(uint160(uint256(_addressesAndAmounts[i]))),
uint256(uint96(bytes12(_addressesAndAmounts[i])))
),
"Batch transfer error"
);
}
return true;
}
/// @dev collectSwapFeesForBTC collects fees in the case of swap WBTC to BTC.
/// @param _destToken The address of target token.
/// @param _incomingAmount The spent amount. (WBTC)
/// @param _minerFee The miner fees of BTC transaction.
/// @param _rewardsAmount The fees that should be paid.
function collectSwapFeesForBTC(
address _destToken,
uint256 _incomingAmount,
uint256 _minerFee,
uint256 _rewardsAmount
) external override onlyOwner returns (bool) {
require(_destToken == address(0), "_destToken should be address(0)");
address _feesToken = WBTC_ADDR;
if (_incomingAmount > 0) {
uint256 swapAmount = _incomingAmount.sub(_rewardsAmount).sub(
_minerFee
);
_swap(WBTC_ADDR, address(0), swapAmount.add(_minerFee));
} else if (_incomingAmount == 0) {
_feesToken = address(0);
}
_rewardsCollection(_feesToken, _rewardsAmount);
return true;
}
/**
* Float part
*/
/// @dev recordIncomingFloat mints LP token.
/// @param _token The address of target token.
/// @param _addressesAndAmountOfFloat The address of recipient and amount.
/// @param _zerofee The flag to accept zero fees.
/// @param _txid The txids which is for recording.
function recordIncomingFloat(
address _token,
bytes32 _addressesAndAmountOfFloat,
bool _zerofee,
bytes32 _txid
) external override onlyOwner priceCheck returns (bool) {
require(whitelist[_token], "_token is invalid");
require(
_issueLPTokensForFloat(
_token,
_addressesAndAmountOfFloat,
_zerofee,
_txid
)
);
return true;
}
/// @dev recordOutcomingFloat burns LP token.
/// @param _token The address of target token.
/// @param _addressesAndAmountOfLPtoken The address of recipient and amount.
/// @param _minerFee The miner fees of BTC transaction.
/// @param _txid The txid which is for recording.
function recordOutcomingFloat(
address _token,
bytes32 _addressesAndAmountOfLPtoken,
uint256 _minerFee,
bytes32 _txid
) external override onlyOwner priceCheck returns (bool) {
require(whitelist[_token], "_token is invalid");
require(
_burnLPTokensForFloat(
_token,
_addressesAndAmountOfLPtoken,
_minerFee,
_txid
)
);
return true;
}
/// @dev distributeNodeRewards sends rewards for Nodes.
function distributeNodeRewards() external override returns (bool) {
// Reduce Gas
uint256 rewardLPTsForNodes = lockedLPTokensForNode.add(
feesLPTokensForNode
);
require(
rewardLPTsForNodes > 0,
"totalRewardLPsForNode is not positive"
);
bytes32[] memory nodeList = getActiveNodes();
uint256 totalStaked = 0;
for (uint256 i = 0; i < nodeList.length; i++) {
totalStaked = totalStaked.add(
uint256(uint96(bytes12(nodeList[i])))
);
}
IBurnableToken(lpToken).mint(address(this), lockedLPTokensForNode);
for (uint256 i = 0; i < nodeList.length; i++) {
IBurnableToken(lpToken).transfer(
address(uint160(uint256(nodeList[i]))),
rewardLPTsForNodes
.mul(uint256(uint96(bytes12(nodeList[i]))))
.div(totalStaked)
);
}
lockedLPTokensForNode = 0;
feesLPTokensForNode = 0;
return true;
}
/**
* Life cycle part
*/
/// @dev recordUTXOSweepMinerFee reduces float amount by collected miner fees.
/// @param _minerFee The miner fees of BTC transaction.
/// @param _txid The txid which is for recording.
function recordUTXOSweepMinerFee(uint256 _minerFee, bytes32 _txid)
public
override
onlyOwner
returns (bool)
{
require(!isTxUsed(_txid), "The txid is already used");
floatAmountOf[address(0)] = floatAmountOf[address(0)].sub(
_minerFee,
"BTC float amount insufficient"
);
_addUsedTx(_txid);
return true;
}
/// @dev churn transfers contract ownership and set variables of the next TSS validator set.
/// @param _newOwner The address of new Owner.
/// @param _rewardAddressAndAmounts The reward addresses and amounts.
/// @param _isRemoved The flags to remove node.
/// @param _churnedInCount The number of next party size of TSS group.
/// @param _tssThreshold The number of next threshold.
/// @param _nodeRewardsRatio The number of rewards ratio for node owners
/// @param _withdrawalFeeBPS The amount of wthdrawal fees.
function churn(
address _newOwner,
bytes32[] memory _rewardAddressAndAmounts,
bool[] memory _isRemoved,
uint8 _churnedInCount,
uint8 _tssThreshold,
uint8 _nodeRewardsRatio,
uint8 _withdrawalFeeBPS
) external override onlyOwner returns (bool) {
require(
_tssThreshold >= tssThreshold && _tssThreshold <= 2**8 - 1,
"_tssThreshold should be >= tssThreshold"
);
require(
_churnedInCount >= _tssThreshold + uint8(1),
"n should be >= t+1"
);
require(
_nodeRewardsRatio >= 0 && _nodeRewardsRatio <= 100,
"_nodeRewardsRatio is not valid"
);
require(
_withdrawalFeeBPS >= 0 && _withdrawalFeeBPS <= 100,
"_withdrawalFeeBPS is invalid"
);
require(
_rewardAddressAndAmounts.length == _isRemoved.length,
"_rewardAddressAndAmounts and _isRemoved length is not match"
);
transferOwnership(_newOwner);
// Update active node list
for (uint256 i = 0; i < _rewardAddressAndAmounts.length; i++) {
(address newNode, ) = _splitToValues(_rewardAddressAndAmounts[i]);
_addNode(newNode, _rewardAddressAndAmounts[i], _isRemoved[i]);
}
bytes32[] memory nodeList = getActiveNodes();
if (nodeList.length > 100) {
revert("Stored node size should be <= 100");
}
churnedInCount = _churnedInCount;
tssThreshold = _tssThreshold;
nodeRewardsRatio = _nodeRewardsRatio;
withdrawalFeeBPS = _withdrawalFeeBPS;
return true;
}
/// @dev isTxUsed sends rewards for Nodes.
/// @param _txid The txid which is for recording.
function isTxUsed(bytes32 _txid) public override view returns (bool) {
return used[_txid];
}
/// @dev getCurrentPriceLP returns the current exchange rate of LP token.
function getCurrentPriceLP()
public
override
view
returns (uint256 nowPrice)
{
(uint256 reserveA, uint256 reserveB) = getFloatReserve(
address(0),
WBTC_ADDR
);
uint256 totalLPs = IBurnableToken(lpToken).totalSupply();
// decimals of totalReserved == 8, lpDecimals == 8, decimals of rate == 8
nowPrice = totalLPs == 0
? initialExchangeRate
: (reserveA.add(reserveB)).mul(lpDecimals).div(
totalLPs.add(lockedLPTokensForNode)
);
return nowPrice;
}
/// @dev getDepositFeeRate returns deposit fees rate
/// @param _token The address of target token.
/// @param _amountOfFloat The amount of float.
function getDepositFeeRate(address _token, uint256 _amountOfFloat)
public
override
view
returns (uint256 depositFeeRate)
{
uint8 isFlip = _checkFlips(_token, _amountOfFloat);
if (isFlip == 1) {
depositFeeRate = _token == WBTC_ADDR ? depositFeesBPS : 0;
} else if (isFlip == 2) {
depositFeeRate = _token == address(0) ? depositFeesBPS : 0;
}
}
/// @dev getFloatReserve returns float reserves
/// @param _tokenA The address of target tokenA.
/// @param _tokenB The address of target tokenB.
function getFloatReserve(address _tokenA, address _tokenB)
public
override
view
returns (uint256 reserveA, uint256 reserveB)
{
(reserveA, reserveB) = (floatAmountOf[_tokenA], floatAmountOf[_tokenB]);
}
/// @dev getActiveNodes returns active nodes list (stakes and amount)
function getActiveNodes() public override view returns (bytes32[] memory) {
uint256 nodeCount = 0;
uint256 count = 0;
// Seek all nodes
for (uint256 i = 0; i < nodeAddrs.length; i++) {
if (nodes[nodeAddrs[i]] != 0x0) {
nodeCount = nodeCount.add(1);
}
}
bytes32[] memory _nodes = new bytes32[](nodeCount);
for (uint256 i = 0; i < nodeAddrs.length; i++) {
if (nodes[nodeAddrs[i]] != 0x0) {
_nodes[count] = nodes[nodeAddrs[i]];
count = count.add(1);
}
}
return _nodes;
}
/// @dev _issueLPTokensForFloat
/// @param _token The address of target token.
/// @param _transaction The recevier address and amount.
/// @param _zerofee The flag to accept zero fees.
/// @param _txid The txid which is for recording.
function _issueLPTokensForFloat(
address _token,
bytes32 _transaction,
bool _zerofee,
bytes32 _txid
) internal returns (bool) {
require(!isTxUsed(_txid), "The txid is already used");
require(_transaction != 0x0, "The transaction is not valid");
// Define target address which is recorded on the tx data (20 bytes)
// Define amountOfFloat which is recorded top on tx data (12 bytes)
(address to, uint256 amountOfFloat) = _splitToValues(_transaction);
// Calculate the amount of LP token
uint256 nowPrice = getCurrentPriceLP();
uint256 amountOfLP = amountOfFloat.mul(priceDecimals).div(nowPrice);
uint256 depositFeeRate = getDepositFeeRate(_token, amountOfFloat);
uint256 depositFees = depositFeeRate != 0
? amountOfLP.mul(depositFeeRate).div(10000)
: 0;
if (_zerofee && depositFees != 0) {
revert();
}
// Send LP tokens to LP
IBurnableToken(lpToken).mint(to, amountOfLP.sub(depositFees));
// Add deposit fees
lockedLPTokensForNode = lockedLPTokensForNode.add(depositFees);
// Add float amount
_addFloat(_token, amountOfFloat);
_addUsedTx(_txid);
emit IssueLPTokensForFloat(
to,
amountOfFloat,
amountOfLP,
nowPrice,
depositFees,
_txid
);
return true;
}
/// @dev _burnLPTokensForFloat
/// @param _token The address of target token.
/// @param _transaction The address of sender and amount.
/// @param _minerFee The miner fees of BTC transaction.
/// @param _txid The txid which is for recording.
function _burnLPTokensForFloat(
address _token,
bytes32 _transaction,
uint256 _minerFee,
bytes32 _txid
) internal returns (bool) {
require(!isTxUsed(_txid), "The txid is already used");
require(_transaction != 0x0, "The transaction is not valid");
// Define target address which is recorded on the tx data (20bytes)
// Define amountLP which is recorded top on tx data (12bytes)
(address to, uint256 amountOfLP) = _splitToValues(_transaction);
// Calculate the amount of LP token
uint256 nowPrice = getCurrentPriceLP();
// Calculate the amountOfFloat
uint256 amountOfFloat = amountOfLP.mul(nowPrice).div(priceDecimals);
uint256 withdrawalFees = amountOfFloat.mul(withdrawalFeeBPS).div(10000);
require(
amountOfFloat.sub(withdrawalFees) >= _minerFee,
"Error: amountOfFloat.sub(withdrawalFees) < _minerFee"
);
uint256 withdrawal = amountOfFloat.sub(withdrawalFees).sub(_minerFee);
(uint256 reserveA, uint256 reserveB) = getFloatReserve(
address(0),
WBTC_ADDR
);
if (_token == address(0)) {
require(
reserveA >= amountOfFloat.sub(withdrawalFees),
"The float balance insufficient."
);
} else if (_token == WBTC_ADDR) {
require(
reserveB >= amountOfFloat.sub(withdrawalFees),
"The float balance insufficient."
);
}
// Collect fees before remove float
_rewardsCollection(_token, withdrawalFees);
// Remove float amount
_removeFloat(_token, amountOfFloat);
// Add txid for recording.
_addUsedTx(_txid);
// WBTC transfer if token address is WBTC_ADDR
if (_token == WBTC_ADDR) {
// _minerFee should be zero
require(
IERC20(_token).transfer(to, withdrawal),
"WBTC balance insufficient"
);
}
// Burn LP tokens
require(IBurnableToken(lpToken).burn(amountOfLP));
emit BurnLPTokensForFloat(
to,
amountOfLP,
amountOfFloat,
nowPrice,
withdrawal,
_txid
);
return true;
}
/// @dev _checkFlips checks whether the fees are activated.
/// @param _token The address of target token.
/// @param _amountOfFloat The amount of float.
function _checkFlips(address _token, uint256 _amountOfFloat)
internal
view
returns (uint8)
{
(uint256 reserveA, uint256 reserveB) = getFloatReserve(
address(0),
WBTC_ADDR
);
uint256 threshold = reserveA
.add(reserveB)
.add(_amountOfFloat)
.mul(2)
.div(3);
if (_token == WBTC_ADDR && reserveB.add(_amountOfFloat) >= threshold) {
return 1; // BTC float insufficient
}
if (_token == address(0) && reserveA.add(_amountOfFloat) >= threshold) {
return 2; // WBTC float insufficient
}
return 0;
}
/// @dev _addFloat updates one side of the float.
/// @param _token The address of target token.
/// @param _amount The amount of float.
function _addFloat(address _token, uint256 _amount) internal {
floatAmountOf[_token] = floatAmountOf[_token].add(_amount);
}
/// @dev _removeFloat remove one side of the float.
/// @param _token The address of target token.
/// @param _amount The amount of float.
function _removeFloat(address _token, uint256 _amount) internal {
floatAmountOf[_token] = floatAmountOf[_token].sub(
_amount,
"_removeFloat: float amount insufficient"
);
}
/// @dev _swap collects swap amount to change float.
/// @param _sourceToken The address of source token
/// @param _destToken The address of target token.
/// @param _swapAmount The amount of swap.
function _swap(
address _sourceToken,
address _destToken,
uint256 _swapAmount
) internal {
floatAmountOf[_destToken] = floatAmountOf[_destToken].sub(
_swapAmount,
"_swap: float amount insufficient"
);
floatAmountOf[_sourceToken] = floatAmountOf[_sourceToken].add(
_swapAmount
);
emit Swap(_sourceToken, _destToken, _swapAmount);
}
/// @dev _rewardsCollection collects tx rewards.
/// @param _feesToken The token address for collection fees.
/// @param _rewardsAmount The amount of rewards.
function _rewardsCollection(address _feesToken, uint256 _rewardsAmount)
internal
{
if (_rewardsAmount == 0) return;
if (_feesToken == lpToken) {
feesLPTokensForNode = feesLPTokensForNode.add(_rewardsAmount);
emit RewardsCollection(_feesToken, _rewardsAmount, 0, 0);
return;
}
// Get current LP token price.
uint256 nowPrice = getCurrentPriceLP();
// Add all fees into pool
floatAmountOf[_feesToken] = floatAmountOf[_feesToken].add(
_rewardsAmount
);
uint256 amountForNodes = _rewardsAmount.mul(nodeRewardsRatio).div(100);
// Alloc LP tokens for nodes as fees
uint256 amountLPTokensForNode = amountForNodes.mul(priceDecimals).div(
nowPrice
);
// Add minted LP tokens for Nodes
lockedLPTokensForNode = lockedLPTokensForNode.add(
amountLPTokensForNode
);
emit RewardsCollection(
_feesToken,
_rewardsAmount,
amountLPTokensForNode,
nowPrice
);
}
/// @dev _addUsedTx updates txid list which is spent. (single hash)
/// @param _txid The array of txid.
function _addUsedTx(bytes32 _txid) internal {
used[_txid] = true;
}
/// @dev _addUsedTxs updates txid list which is spent. (multiple hashes)
/// @param _txids The array of txid.
function _addUsedTxs(bytes32[] memory _txids) internal {
for (uint256 i = 0; i < _txids.length; i++) {
used[_txids[i]] = true;
}
}
/// @dev _addNode updates a staker's info.
/// @param _addr The address of staker.
/// @param _data The data of staker.
/// @param _remove The flag to remove node.
function _addNode(
address _addr,
bytes32 _data,
bool _remove
) internal returns (bool) {
if (_remove) {
delete nodes[_addr];
return true;
}
if (!isInList[_addr]) {
nodeAddrs.push(_addr);
isInList[_addr] = true;
}
if (nodes[_addr] == 0x0) {
nodes[_addr] = _data;
}
return true;
}
/// @dev _splitToValues returns address and amount of staked SWINGBYs
/// @param _data The info of a staker.
function _splitToValues(bytes32 _data)
internal
pure
returns (address, uint256)
{
return (
address(uint160(uint256(_data))),
uint256(uint96(bytes12(_data)))
);
}
/// @dev The contract doesn't allow receiving Ether.
fallback() external {
revert();
}
}
{
"compilationTarget": {
"SwapContract.sol": "SwapContract"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_lpToken","type":"address"},{"internalType":"address","name":"_wbtc","type":"address"},{"internalType":"uint256","name":"_existingBTCFloat","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfLP","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfFloat","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"currentPriceLP","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"withdrawal","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"txid","type":"bytes32"}],"name":"BurnLPTokensForFloat","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfFloat","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfLP","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"currentPriceLP","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"depositFees","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"txid","type":"bytes32"}],"name":"IssueLPTokensForFloat","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"feesToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"rewards","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountLPTokensForNode","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"currentPriceLP","type":"uint256"}],"name":"RewardsCollection","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Swap","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[],"name":"WBTC_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"},{"internalType":"bytes32[]","name":"_rewardAddressAndAmounts","type":"bytes32[]"},{"internalType":"bool[]","name":"_isRemoved","type":"bool[]"},{"internalType":"uint8","name":"_churnedInCount","type":"uint8"},{"internalType":"uint8","name":"_tssThreshold","type":"uint8"},{"internalType":"uint8","name":"_nodeRewardsRatio","type":"uint8"},{"internalType":"uint8","name":"_withdrawalFeeBPS","type":"uint8"}],"name":"churn","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"churnedInCount","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_destToken","type":"address"},{"internalType":"uint256","name":"_incomingAmount","type":"uint256"},{"internalType":"uint256","name":"_minerFee","type":"uint256"},{"internalType":"uint256","name":"_rewardsAmount","type":"uint256"}],"name":"collectSwapFeesForBTC","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"depositFeesBPS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"distributeNodeRewards","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feesLPTokensForNode","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getActiveNodes","outputs":[{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentPriceLP","outputs":[{"internalType":"uint256","name":"nowPrice","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amountOfFloat","type":"uint256"}],"name":"getDepositFeeRate","outputs":[{"internalType":"uint256","name":"depositFeeRate","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_tokenA","type":"address"},{"internalType":"address","name":"_tokenB","type":"address"}],"name":"getFloatReserve","outputs":[{"internalType":"uint256","name":"reserveA","type":"uint256"},{"internalType":"uint256","name":"reserveB","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"initialExchangeRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_txid","type":"bytes32"}],"name":"isTxUsed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lockedLPTokensForNode","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lpToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_destToken","type":"address"},{"internalType":"bytes32[]","name":"_addressesAndAmounts","type":"bytes32[]"},{"internalType":"uint256","name":"_totalSwapped","type":"uint256"},{"internalType":"uint256","name":"_rewardsAmount","type":"uint256"},{"internalType":"bytes32[]","name":"_redeemedFloatTxIds","type":"bytes32[]"}],"name":"multiTransferERC20TightlyPacked","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"nodeRewardsRatio","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"bytes32","name":"_addressesAndAmountOfFloat","type":"bytes32"},{"internalType":"bool","name":"_zerofee","type":"bool"},{"internalType":"bytes32","name":"_txid","type":"bytes32"}],"name":"recordIncomingFloat","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"bytes32","name":"_addressesAndAmountOfLPtoken","type":"bytes32"},{"internalType":"uint256","name":"_minerFee","type":"uint256"},{"internalType":"bytes32","name":"_txid","type":"bytes32"}],"name":"recordOutcomingFloat","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_minerFee","type":"uint256"},{"internalType":"bytes32","name":"_txid","type":"bytes32"}],"name":"recordUTXOSweepMinerFee","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_destToken","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_totalSwapped","type":"uint256"},{"internalType":"uint256","name":"_rewardsAmount","type":"uint256"},{"internalType":"bytes32[]","name":"_redeemedFloatTxIds","type":"bytes32[]"}],"name":"singleTransferERC20","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"tssThreshold","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"whitelist","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawalFeeBPS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"}]