diff --git a/contracts/interfaces/IScdMcdMigration.sol b/contracts/interfaces/IScdMcdMigration.sol index 215bed94..e52a217f 100644 --- a/contracts/interfaces/IScdMcdMigration.sol +++ b/contracts/interfaces/IScdMcdMigration.sol @@ -8,3 +8,7 @@ interface IScdMcdMigration { interface IDaiAdapter { function dai() public returns (address); } + +interface ISaiTop { + function caged() public returns (uint256); +} diff --git a/contracts/upgradeable_contracts/BaseBridgeValidators.sol b/contracts/upgradeable_contracts/BaseBridgeValidators.sol index a55710d6..7e6bb967 100644 --- a/contracts/upgradeable_contracts/BaseBridgeValidators.sol +++ b/contracts/upgradeable_contracts/BaseBridgeValidators.sol @@ -102,4 +102,23 @@ contract BaseBridgeValidators is InitializableBridge, Ownable { function setNextValidator(address _prevValidator, address _validator) internal { addressStorage[keccak256(abi.encodePacked("validatorsList", _prevValidator))] = _validator; } + + function isValidatorDuty(address _validator) external view returns (bool) { + uint256 counter = 0; + address next = getNextValidator(F_ADDR); + require(next != address(0)); + + while (next != F_ADDR) { + if (next == _validator) { + return (block.number % validatorCount() == counter); + } + + next = getNextValidator(next); + counter++; + + require(next != address(0)); + } + + return false; + } } diff --git a/contracts/upgradeable_contracts/erc20_to_native/ForeignBridgeErcToNative.sol b/contracts/upgradeable_contracts/erc20_to_native/ForeignBridgeErcToNative.sol index 122cc45e..8c17fc75 100644 --- a/contracts/upgradeable_contracts/erc20_to_native/ForeignBridgeErcToNative.sol +++ b/contracts/upgradeable_contracts/erc20_to_native/ForeignBridgeErcToNative.sol @@ -8,6 +8,9 @@ import "../../interfaces/IScdMcdMigration.sol"; contract ForeignBridgeErcToNative is BasicForeignBridge, ERC20Bridge, OtherSideBridgeStorage { event TokensSwapped(address indexed from, address indexed to, uint256 value); + bytes32 internal constant MIN_HDTOKEN_BALANCE = 0x48649cf195feb695632309f41e61252b09f537943654bde13eb7bb1bca06964e; // keccak256(abi.encodePacked("minHDTokenBalance")) + bytes4 internal constant SWAP_TOKENS = 0x73d00224; // swapTokens() + function initialize( address _validatorContract, address _erc20token, @@ -61,6 +64,11 @@ contract ForeignBridgeErcToNative is BasicForeignBridge, ERC20Bridge, OtherSideB function claimTokens(address _token, address _to) public { require(_token != address(erc20token())); + if (_token == address(halfDuplexErc20token())) { + // SCD is not claimable if the bridge accepts deposits of this token + // solhint-disable-next-line not-rely-on-time + require(!isTokenSwapAllowed(now)); + } super.claimTokens(_token, _to); } @@ -71,7 +79,13 @@ contract ForeignBridgeErcToNative is BasicForeignBridge, ERC20Bridge, OtherSideB ) internal returns (bool) { setTotalExecutedPerDay(getCurrentDay(), totalExecutedPerDay(getCurrentDay()).add(_amount)); uint256 amount = _amount.div(10**decimalShift()); - return erc20token().transfer(_recipient, amount); + bool res = erc20token().transfer(_recipient, amount); + + if (AddressUtils.isContract(halfDuplexErc20token()) && tokenBalance(halfDuplexErc20token()) > 0) { + address(this).call(abi.encodeWithSelector(SWAP_TOKENS)); + } + + return res; } function onFailedMessage(address, uint256, bytes32) internal { @@ -102,4 +116,100 @@ contract ForeignBridgeErcToNative is BasicForeignBridge, ERC20Bridge, OtherSideB emit TokensSwapped(saiContract, erc20token(), curBalance); boolStorage[storageAddress] = true; } + + function saiTopContract() internal pure returns (ISaiTop) { + return ISaiTop(0x9b0ccf7C8994E19F39b2B4CF708e0A7DF65fA8a3); + } + + function isTokenSwapAllowed(uint256 _ts) public view returns (bool) { + uint256 esTs = saiTopContract().caged(); + if ((esTs > 0) && (_ts > esTs)) { + return false; + } + return true; + } + + function halfDuplexErc20token() public pure returns (ERC20) { + return ERC20(0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359); + } + + function setMinHDTokenBalance(uint256 _minBalance) external onlyOwner { + uintStorage[MIN_HDTOKEN_BALANCE] = _minBalance; + } + + function minHDTokenBalance() public view returns (uint256) { + return uintStorage[MIN_HDTOKEN_BALANCE]; + } + + function isHDTokenBalanceAboveMinBalance() public view returns (bool) { + if (tokenBalance(halfDuplexErc20token()) > minHDTokenBalance()) { + return true; + } + return false; + } + + function tokenBalance(ERC20 _token) internal view returns (uint256) { + return _token.balanceOf(address(this)); + } + + function migrationContract() internal pure returns (IScdMcdMigration) { + return IScdMcdMigration(0xc73e0383F3Aff3215E6f04B0331D58CeCf0Ab849); + } + + function swapTokens() public { + // solhint-disable-next-line not-rely-on-time + require(isTokenSwapAllowed(now)); + + IScdMcdMigration mcdMigrationContract = migrationContract(); + ERC20 hdToken = halfDuplexErc20token(); + ERC20 fdToken = erc20token(); + + uint256 curHDTokenBalance = tokenBalance(hdToken); + require(curHDTokenBalance > 0); + + uint256 curFDTokenBalance = tokenBalance(fdToken); + + require(hdToken.approve(mcdMigrationContract, curHDTokenBalance)); + mcdMigrationContract.swapSaiToDai(curHDTokenBalance); + + require(tokenBalance(fdToken) - curFDTokenBalance == curHDTokenBalance); + + emit TokensSwapped(hdToken, fdToken, curHDTokenBalance); + } + + function relayTokens(address _from, address _receiver, uint256 _amount, address _token) external { + require(_from == msg.sender || _from == _receiver); + _relayTokens(_from, _receiver, _amount, _token); + } + + function relayTokens(address _receiver, uint256 _amount, address _token) external { + _relayTokens(msg.sender, _receiver, _amount, _token); + } + + function _relayTokens(address _sender, address _receiver, uint256 _amount, address _token) internal { + require(_receiver != bridgeContractOnOtherSide()); + require(_receiver != address(0)); + require(_receiver != address(this)); + require(_amount > 0); + require(withinLimit(_amount)); + + ERC20 tokenToOperate = ERC20(_token); + ERC20 hdToken = halfDuplexErc20token(); + ERC20 fdToken = erc20token(); + + if (tokenToOperate == ERC20(0x0)) { + tokenToOperate = fdToken; + } + + require(tokenToOperate == fdToken || tokenToOperate == hdToken); + + setTotalSpentPerDay(getCurrentDay(), totalSpentPerDay(getCurrentDay()).add(_amount)); + + tokenToOperate.transferFrom(_sender, address(this), _amount); + emit UserRequestForAffirmation(_receiver, _amount); + + if (tokenToOperate == hdToken) { + swapTokens(); + } + } }