ETH Price: $2,575.00 (+1.04%)

Transaction Decoder

Block:
11863633 at Feb-15-2021 08:55:51 PM +UTC
Transaction Fee:
0.038802456 ETH $99.92
Gas Used:
279,960 Gas / 138.6 Gwei

Emitted Events:

171 ReversibleICO.PendingContributionAdded( participantAddress=[Sender] 0x41d1b9deb85b56c61ad4e26559ee3843c5198e13, amount=7750000000000000000, contributionId=8, stageId=9 )
172 ReversibleICO.ContributionsAccepted( participantAddress=[Sender] 0x41d1b9deb85b56c61ad4e26559ee3843c5198e13, ethAmount=7750000000000000000, tokenAmount=3690476190476190476190, stageId=9 )
173 ReversibleICOToken.Sent( operator=[Receiver] ReversibleICO, from=[Receiver] ReversibleICO, to=[Sender] 0x41d1b9deb85b56c61ad4e26559ee3843c5198e13, amount=3690476190476190476190, data=0x, operatorData=0x )
174 ReversibleICOToken.Transfer( from=[Receiver] ReversibleICO, to=[Sender] 0x41d1b9deb85b56c61ad4e26559ee3843c5198e13, value=3690476190476190476190 )

Account State Difference:

  Address   Before After State Difference Code
0x41D1b9de...3C5198e13
9.086581792271449919 Eth
Nonce: 10
1.297779336271449919 Eth
Nonce: 11
7.788802456
(Spark Pool)
120.741718673390856052 Eth120.780521129390856052 Eth0.038802456
0xA8b91968...5aec0be6D
0xE417b912...191b2CdF6
(LUKSO: Reversible ICO)
1,819.083951827471469967 Eth1,826.833951827471469967 Eth7.75

Execution Trace

ETH 7.75 ReversibleICO.CALL( )
  • ReversibleICOToken.send( recipient=0x41D1b9deb85B56C61ad4e26559ee3843C5198e13, amount=3690476190476190476190, data=0x )
    • ERC1820Registry.getInterfaceImplementer( _addr=0xE417b912F6cB6592ec2D71dbF6F2B48191b2CdF6, _interfaceHash=29DDB589B1FB5FC7CF394961C1ADF5F8C6454761ADF795E67FE149F658ABE895 ) => ( 0x0000000000000000000000000000000000000000 )
    • ReversibleICO.getParticipantReservedTokens( _participantAddress=0xE417b912F6cB6592ec2D71dbF6F2B48191b2CdF6 ) => ( 0 )
    • ERC1820Registry.getInterfaceImplementer( _addr=0x41D1b9deb85B56C61ad4e26559ee3843C5198e13, _interfaceHash=B281FC8C12954D22544DB45DE3159A39272895B169A852B314F9CC762E44C53B ) => ( 0x0000000000000000000000000000000000000000 )
      [ReversibleICO (ln:436)]
      File 1 of 3: ReversibleICO
      /*
       * Submitted for verification at etherscan.io on 2020-06-10
       *
       *   ________            ____                           _ __    __        ______________
       *  /_  __/ /_  ___     / __ \___ _   _____  __________(_) /_  / /__     /  _/ ____/ __ \
       *   / / / __ \/ _ \   / /_/ / _ \ | / / _ \/ ___/ ___/ / __ \/ / _ \    / // /   / / / /
       *  / / / / / /  __/  / _, _/  __/ |/ /  __/ /  (__  ) / /_/ / /  __/  _/ // /___/ /_/ /
       * /_/ /_/ /_/\___/  /_/ |_|\___/|___/\___/_/  /____/_/_.___/_/\___/  /___/\____/\____/
       *
       *
       * source      https://github.com/lukso-network/rICO-smart-contracts
       * @name       Reversible ICO
       * @author     Fabian Vogelsteller <@frozeman>, Micky Socaci <[email protected]>, Marjorie Hernandez <[email protected]>
       * @license    Apache 2.0
       *
       * Readme more about it here https://medium.com/lukso/rico-the-reversible-ico-5392bf64318b
       */
      
      pragma solidity ^0.5.0;
      
      
      library SafeMath {
          
          function add(uint256 a, uint256 b) internal pure returns (uint256) {
              uint256 c = a + b;
              require(c >= a, "SafeMath: addition overflow");
      
              return c;
          }
      
          
          function sub(uint256 a, uint256 b) internal pure returns (uint256) {
              require(b <= a, "SafeMath: subtraction overflow");
              uint256 c = a - b;
      
              return c;
          }
      
          
          function mul(uint256 a, uint256 b) internal pure returns (uint256) {
              
              
              
              if (a == 0) {
                  return 0;
              }
      
              uint256 c = a * b;
              require(c / a == b, "SafeMath: multiplication overflow");
      
              return c;
          }
      
          
          function div(uint256 a, uint256 b) internal pure returns (uint256) {
              
              require(b > 0, "SafeMath: division by zero");
              uint256 c = a / b;
              
      
              return c;
          }
      
          
          function mod(uint256 a, uint256 b) internal pure returns (uint256) {
              require(b != 0, "SafeMath: modulo by zero");
              return a % b;
          }
      }
      
      interface IERC777 {
          
          function name() external view returns (string memory);
      
          
          function symbol() external view returns (string memory);
      
          
          function granularity() external view returns (uint256);
      
          
          function totalSupply() external view returns (uint256);
      
          
          function balanceOf(address owner) external view returns (uint256);
      
          
          function send(address recipient, uint256 amount, bytes calldata data) external;
      
          
          function burn(uint256 amount, bytes calldata data) external;
      
          
          function isOperatorFor(address operator, address tokenHolder) external view returns (bool);
      
          
          function authorizeOperator(address operator) external;
      
          
          function revokeOperator(address operator) external;
      
          
          function defaultOperators() external view returns (address[] memory);
      
          
          function operatorSend(
              address sender,
              address recipient,
              uint256 amount,
              bytes calldata data,
              bytes calldata operatorData
          ) external;
      
          
          function operatorBurn(
              address account,
              uint256 amount,
              bytes calldata data,
              bytes calldata operatorData
          ) external;
      
          event Sent(
              address indexed operator,
              address indexed from,
              address indexed to,
              uint256 amount,
              bytes data,
              bytes operatorData
          );
      
          event Minted(address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData);
      
          event Burned(address indexed operator, address indexed from, uint256 amount, bytes data, bytes operatorData);
      
          event AuthorizedOperator(address indexed operator, address indexed tokenHolder);
      
          event RevokedOperator(address indexed operator, address indexed tokenHolder);
      }
      
      interface IERC777Recipient {
          
          function tokensReceived(
              address operator,
              address from,
              address to,
              uint256 amount,
              bytes calldata userData,
              bytes calldata operatorData
          ) external;
      }
      
      interface IERC1820Registry {
          
          function setManager(address account, address newManager) external;
      
          
          function getManager(address account) external view returns (address);
      
          
          function setInterfaceImplementer(address account, bytes32 interfaceHash, address implementer) external;
      
          
          function getInterfaceImplementer(address account, bytes32 interfaceHash) external view returns (address);
      
          
          function interfaceHash(string calldata interfaceName) external pure returns (bytes32);
      
          
          function updateERC165Cache(address account, bytes4 interfaceId) external;
      
          
          function implementsERC165Interface(address account, bytes4 interfaceId) external view returns (bool);
      
          
          function implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool);
      
          event InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer);
      
          event ManagerChanged(address indexed account, address indexed newManager);
      }
      
      contract ReversibleICO is IERC777Recipient {
      
      
          
          using SafeMath for uint256;
      
          
          IERC1820Registry private ERC1820 = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
          bytes32 constant private TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient");
      
      
          
          
          bool public initialized;
      
          
          bool public frozen;
          uint256 public frozenPeriod;
          uint256 public freezeStart;
      
      
          
          
          address public deployingAddress;
          
          address public tokenAddress;
          
          address public projectAddress;
          
          address public whitelistingAddress;
          
          address public freezerAddress;
          
          address public rescuerAddress;
      
      
          
          
          uint256 public initialTokenSupply;
          
          uint256 public tokenSupply;
          
          uint256 public committedETH;
          
          uint256 public pendingETH;
          
          uint256 public canceledETH;
          
          uint256 public withdrawnETH;
          
          uint256 public projectWithdrawCount;
          
          uint256 public projectWithdrawnETH;
      
          
          uint256 public minContribution = 0.1 ether;
          uint256 public maxContribution = 4000 ether;
      
          mapping(uint8 => Stage) public stages;
          uint8 public stageCount;
      
          
          mapping(address => Participant) public participants;
          
          mapping(uint256 => address) public participantsById;
          
          uint256 public participantCount;
      
          
          
          uint256 public commitPhasePrice;
          
          uint256 public commitPhaseStartBlock;
          
          uint256 public commitPhaseEndBlock;
          
          uint256 public commitPhaseBlockCount;
      
      
          
          
          uint256 public buyPhaseStartBlock;
          
          uint256 public buyPhaseEndBlock;
          
          uint256 public buyPhaseBlockCount;
      
          
          
          uint256 internal _projectCurrentlyReservedETH;
          
          uint256 internal _projectUnlockedETH;
          
          uint256 internal _projectLastBlock;
      
      
          
      
          
          struct Stage {
              uint256 tokenLimit; 
              uint256 tokenPrice;
          }
      
          
          struct Participant {
              bool whitelisted;
              uint32 contributions;
              uint32 withdraws;
              uint256 firstContributionBlock;
              uint256 reservedTokens;
              uint256 committedETH;
              uint256 pendingETH;
      
              uint256 _currentReservedTokens;
              uint256 _unlockedTokens;
              uint256 _lastBlock;
      
              mapping(uint8 => ParticipantStageDetails) stages;
          }
      
          struct ParticipantStageDetails {
              uint256 pendingETH;
          }
      
          
          event PendingContributionAdded(address indexed participantAddress, uint256 indexed amount, uint32 indexed contributionId, uint8 stageId);
          event PendingContributionsCanceled(address indexed participantAddress, uint256 indexed amount, uint32 indexed contributionId);
      
          event WhitelistApproved(address indexed participantAddress, uint256 indexed pendingETH, uint32 indexed contributions);
          event WhitelistRejected(address indexed participantAddress, uint256 indexed pendingETH, uint32 indexed contributions);
      
          event ContributionsAccepted(address indexed participantAddress, uint256 indexed ethAmount, uint256 indexed tokenAmount, uint8 stageId);
      
          event ProjectWithdraw(address indexed projectAddress, uint256 indexed amount, uint32 indexed withdrawCount);
          event ParticipantWithdraw(address indexed participantAddress, uint256 indexed ethAmount, uint256 indexed tokenAmount, uint32 withdrawCount);
      
          event StageChanged(uint8 indexed stageId, uint256 indexed tokenLimit, uint256 indexed tokenPrice, uint256 effectiveBlockNumber);
          event WhitelistingAddressChanged(address indexed whitelistingAddress, uint8 indexed stageId, uint256 indexed effectiveBlockNumber);
          event FreezerAddressChanged(address indexed freezerAddress, uint8 indexed stageId, uint256 indexed effectiveBlockNumber);
      
          event SecurityFreeze(address indexed freezerAddress, uint8 indexed stageId, uint256 indexed effectiveBlockNumber);
          event SecurityUnfreeze(address indexed freezerAddress, uint8 indexed stageId, uint256 indexed effectiveBlockNumber);
          event SecurityDisableEscapeHatch(address indexed freezerAddress, uint8 indexed stageId, uint256 indexed effectiveBlockNumber);
          event SecurityEscapeHatch(address indexed rescuerAddress, address indexed to, uint8 indexed stageId, uint256 effectiveBlockNumber);
      
      
          event TransferEvent (
              uint8 indexed typeId,
              address indexed relatedAddress,
              uint256 indexed value
          );
      
          enum TransferTypes {
              NOT_SET, 
              WHITELIST_REJECTED, 
              CONTRIBUTION_CANCELED, 
              CONTRIBUTION_ACCEPTED_OVERFLOW, 
              PARTICIPANT_WITHDRAW, 
              PARTICIPANT_WITHDRAW_OVERFLOW, 
              PROJECT_WITHDRAWN, 
              FROZEN_ESCAPEHATCH_TOKEN, 
              FROZEN_ESCAPEHATCH_ETH 
          }
      
      
          
      
          
          constructor() public {
              deployingAddress = msg.sender;
              ERC1820.setInterfaceImplementer(address(this), TOKENS_RECIPIENT_INTERFACE_HASH, address(this));
          }
      
          
          function init(
              address _tokenAddress,
              address _whitelistingAddress,
              address _freezerAddress,
              address _rescuerAddress,
              address _projectAddress,
              uint256 _commitPhaseStartBlock,
              uint256 _buyPhaseStartBlock,
              uint256 _buyPhaseEndBlock,
              uint256 _initialPrice,
              uint8 _stageCount, 
              uint256 _stageTokenLimitIncrease,
              uint256 _stagePriceIncrease
          )
          public
          onlyDeployingAddress
          isNotInitialized
          {
              require(_tokenAddress != address(0), "_tokenAddress cannot be 0x");
              require(_whitelistingAddress != address(0), "_whitelistingAddress cannot be 0x");
              require(_freezerAddress != address(0), "_freezerAddress cannot be 0x");
              require(_rescuerAddress != address(0), "_rescuerAddress cannot be 0x");
              require(_projectAddress != address(0), "_projectAddress cannot be 0x");
              
      
              
              tokenAddress = _tokenAddress;
              whitelistingAddress = _whitelistingAddress;
              freezerAddress = _freezerAddress;
              rescuerAddress = _rescuerAddress;
              projectAddress = _projectAddress;
      
              
              commitPhaseStartBlock = _commitPhaseStartBlock;
              commitPhaseEndBlock = _buyPhaseStartBlock.sub(1);
              commitPhaseBlockCount = commitPhaseEndBlock.sub(commitPhaseStartBlock).add(1);
              commitPhasePrice = _initialPrice;
      
              stageCount = _stageCount;
      
      
              
              Stage storage commitPhase = stages[0];
              commitPhase.tokenLimit = _stageTokenLimitIncrease;
              commitPhase.tokenPrice = _initialPrice;
      
      
              
              uint256 previousStageTokenLimit = _stageTokenLimitIncrease;
      
              
              for (uint8 i = 1; i <= _stageCount; i++) {
                  
                  Stage storage byStage = stages[i];
                  
                  byStage.tokenLimit = previousStageTokenLimit.add(_stageTokenLimitIncrease);
                  
                  previousStageTokenLimit = byStage.tokenLimit;
                  
                  byStage.tokenPrice = _initialPrice.add(_stagePriceIncrease.mul(i));
              }
      
              
              
              buyPhaseStartBlock = _buyPhaseStartBlock;
              
              buyPhaseEndBlock = _buyPhaseEndBlock;
              
              buyPhaseBlockCount = buyPhaseEndBlock.sub(buyPhaseStartBlock).add(1);
      
              
              initialized = true;
          }
      
          
      
          
      
          
          function()
          external
          payable
          isInitialized
          isNotFrozen
          {
              Participant storage participantStats = participants[msg.sender];
      
              
              if (participantStats.whitelisted == true && participantStats.contributions > 0) {
                  commit();
      
              
              } else {
                  require(msg.value < minContribution, 'To contribute call commit() [0x3c7a3aff] and send ETH along.');
      
                  
                  cancelPendingContributions(msg.sender, msg.value);
              }
          }
      
          
          function tokensReceived(
              address,
              address _from,
              address,
              uint256 _amount,
              bytes calldata,
              bytes calldata
          )
          external
          isInitialized
          isNotFrozen
          {
              
              
              require(msg.sender == tokenAddress, "Unknown token contract sent tokens.");
      
              
              if (_from == projectAddress) {
                  
                  tokenSupply = tokenSupply.add(_amount);
                  initialTokenSupply = initialTokenSupply.add(_amount);
      
                  
              } else {
                  withdraw(_from, _amount);
              }
          }
      
          
          function commit()
          public
          payable
          isInitialized
          isNotFrozen
          isRunning
          {
              
              require(msg.value >= minContribution, "Value sent is less than the minimum contribution.");
      
              
              uint8 currentStage = getCurrentStage();
              Participant storage participantStats = participants[msg.sender];
              ParticipantStageDetails storage byStage = participantStats.stages[currentStage];
      
              require(participantStats.committedETH.add(msg.value) <= maxContribution, "Value sent is larger than the maximum contribution.");
      
              
              if (participantStats.contributions == 0) {
                  
                  participantsById[participantCount] = msg.sender;
                  
                  participantCount++;
              }
      
              
              participantStats.contributions++;
              participantStats.pendingETH = participantStats.pendingETH.add(msg.value);
              byStage.pendingETH = byStage.pendingETH.add(msg.value);
      
              
              pendingETH = pendingETH.add(msg.value);
      
              emit PendingContributionAdded(
                  msg.sender,
                  msg.value,
                  uint32(participantStats.contributions),
                  currentStage
              );
      
              
              if (participantStats.whitelisted == true) {
                  acceptContributions(msg.sender);
              }
          }
      
          
          function cancel()
          external
          payable
          isInitialized
          isNotFrozen
          {
              cancelPendingContributions(msg.sender, msg.value);
          }
      
          
          function whitelist(address[] calldata _addresses, bool _approve)
          external
          onlyWhitelistingAddress
          isInitialized
          isNotFrozen
          isRunning
          {
              
              require(_addresses.length > 0, "No addresses given to whitelist.");
      
              for (uint256 i = 0; i < _addresses.length; i++) {
                  address participantAddress = _addresses[i];
      
                  Participant storage participantStats = participants[participantAddress];
      
                  if (_approve) {
                      if (participantStats.whitelisted == false) {
                          
                          participantStats.whitelisted = true;
                          emit WhitelistApproved(participantAddress, participantStats.pendingETH, uint32(participantStats.contributions));
                      }
      
                      
                      acceptContributions(participantAddress);
      
                  } else {
                      participantStats.whitelisted = false;
                      emit WhitelistRejected(participantAddress, participantStats.pendingETH, uint32(participantStats.contributions));
      
                      
                      cancelPendingContributions(participantAddress, 0);
                  }
              }
          }
      
          
          function projectTokenWithdraw(uint256 _tokenAmount)
          external
          onlyProjectAddress
          isInitialized
          {
              require(_tokenAmount <= tokenSupply, "Requested amount too high, not enough tokens available.");
      
              
              tokenSupply = tokenSupply.sub(_tokenAmount);
              initialTokenSupply = initialTokenSupply.sub(_tokenAmount);
      
              
              
              IERC777(tokenAddress).send(projectAddress, _tokenAmount, "");
          }
      
          
          function projectWithdraw(uint256 _ethAmount)
          external
          onlyProjectAddress
          isInitialized
          isNotFrozen
          {
              
              calcProjectAllocation();
      
              
              uint256 availableForWithdraw = _projectUnlockedETH.sub(projectWithdrawnETH);
      
              require(_ethAmount <= availableForWithdraw, "Requested amount too high, not enough ETH unlocked.");
      
              
              projectWithdrawCount++;
              projectWithdrawnETH = projectWithdrawnETH.add(_ethAmount);
      
              
              emit ProjectWithdraw(
                  projectAddress,
                  _ethAmount,
                  uint32(projectWithdrawCount)
              );
              emit TransferEvent(
                  uint8(TransferTypes.PROJECT_WITHDRAWN),
                  projectAddress,
                  _ethAmount
              );
      
              
              address(uint160(projectAddress)).transfer(_ethAmount);
          }
      
      
          function changeStage(uint8 _stageId, uint256 _tokenLimit, uint256 _tokenPrice)
          external
          onlyProjectAddress
          isInitialized
          {
              stages[_stageId].tokenLimit = _tokenLimit;
              stages[_stageId].tokenPrice = _tokenPrice;
      
              if(_stageId > stageCount) {
                  stageCount = _stageId;
              }
      
              emit StageChanged(_stageId, _tokenLimit, _tokenPrice, getCurrentEffectiveBlockNumber());
          }
      
      
          function changeWhitelistingAddress(address _newAddress)
          external
          onlyProjectAddress
          isInitialized
          {
              whitelistingAddress = _newAddress;
              emit WhitelistingAddressChanged(whitelistingAddress, getCurrentStage(), getCurrentEffectiveBlockNumber());
          }
      
      
          function changeFreezerAddress(address _newAddress)
          external
          onlyProjectAddress
          isInitialized
          {
              freezerAddress = _newAddress;
              emit FreezerAddressChanged(freezerAddress, getCurrentStage(), getCurrentEffectiveBlockNumber());
          }
      
      
          
      
          
          function freeze()
          external
          onlyFreezerAddress
          isNotFrozen
          {
              frozen = true;
              freezeStart = getCurrentEffectiveBlockNumber();
      
              
              emit SecurityFreeze(freezerAddress, getCurrentStage(), freezeStart);
          }
      
          
          function unfreeze()
          external
          onlyFreezerAddress
          isFrozen
          {
              uint256 currentBlock = getCurrentEffectiveBlockNumber();
      
              frozen = false;
              frozenPeriod = frozenPeriod.add(
                  currentBlock.sub(freezeStart)
              );
      
              
              emit SecurityUnfreeze(freezerAddress, getCurrentStage(), currentBlock);
          }
      
          
          function disableEscapeHatch()
          external
          onlyFreezerAddress
          isNotFrozen
          {
              freezerAddress = address(0);
              rescuerAddress = address(0);
      
              
              emit SecurityDisableEscapeHatch(freezerAddress, getCurrentStage(), getCurrentEffectiveBlockNumber());
          }
      
          
          function escapeHatch(address _to)
          external
          onlyRescuerAddress
          isFrozen
          {
              require(getCurrentEffectiveBlockNumber() == freezeStart.add(18000), 'Let it cool.. Wait at least ~3 days (18000 blk) before moving anything.');
      
              uint256 tokenBalance = IERC777(tokenAddress).balanceOf(address(this));
              uint256 ethBalance = address(this).balance;
      
              
              
              IERC777(tokenAddress).send(_to, tokenBalance, "");
      
              
              address(uint160(_to)).transfer(ethBalance);
      
              
              emit SecurityEscapeHatch(rescuerAddress, _to, getCurrentStage(), getCurrentEffectiveBlockNumber());
      
              emit TransferEvent(uint8(TransferTypes.FROZEN_ESCAPEHATCH_TOKEN), _to, tokenBalance);
              emit TransferEvent(uint8(TransferTypes.FROZEN_ESCAPEHATCH_ETH), _to, ethBalance);
          }
      
      
          
      
          
          function getUnlockedProjectETH() public view returns (uint256) {
      
              
              uint256 newlyUnlockedEth = calcUnlockedAmount(_projectCurrentlyReservedETH, _projectLastBlock);
      
              return _projectUnlockedETH
              .add(newlyUnlockedEth);
          }
      
          
          function getAvailableProjectETH() public view returns (uint256) {
              return getUnlockedProjectETH()
                  .sub(projectWithdrawnETH);
          }
      
          
          function getParticipantReservedTokens(address _participantAddress) public view returns (uint256) {
              Participant storage participantStats = participants[_participantAddress];
      
              if(participantStats._currentReservedTokens == 0) {
                  return 0;
              }
      
              return participantStats._currentReservedTokens.sub(
                  calcUnlockedAmount(participantStats._currentReservedTokens, participantStats._lastBlock)
              );
          }
      
          
          function getParticipantUnlockedTokens(address _participantAddress) public view returns (uint256) {
              Participant storage participantStats = participants[_participantAddress];
      
              return participantStats._unlockedTokens.add(
                  calcUnlockedAmount(participantStats._currentReservedTokens, participantStats._lastBlock)
              );
          }
      
          
          function getAvailableTokenAtCurrentStage() public view returns (uint256) {
              return stages[getCurrentStage()].tokenLimit.sub(
                  initialTokenSupply.sub(tokenSupply)
              );
          }
      
      
          
          function getCurrentStage() public view returns (uint8) {
              return getStageByTokenLimit(
                  initialTokenSupply.sub(tokenSupply)
              );
          }
      
          
          function getCurrentPrice() public view returns (uint256) {
              return getPriceAtStage(getCurrentStage());
          }
      
      
          
          function getPriceAtStage(uint8 _stageId) public view returns (uint256) {
              if (_stageId <= stageCount) {
                  return stages[_stageId].tokenPrice;
              }
              return stages[stageCount].tokenPrice;
          }
      
      
          
          function getPriceForTokenLimit(uint256 _tokenLimit) public view returns (uint256) {
              return getPriceAtStage(getStageByTokenLimit(_tokenLimit));
          }
      
          
          function getStageByTokenLimit(uint256 _tokenLimit) public view returns (uint8) {
      
              
              for (uint8 stageId = 0; stageId <= stageCount; stageId++) {
                  if(_tokenLimit <= stages[stageId].tokenLimit) {
                      return stageId;
                  }
              }
              
              return stageCount;
          }
      
          
          function committableEthAtStage(uint8 _stageId, uint8 _currentStage) public view returns (uint256) {
              uint256 supply;
      
              
              if(_stageId < _currentStage) {
                  return 0;
      
              
              } else if(_stageId >= stageCount) {
                  supply = tokenSupply;
      
              
              } else if(_stageId == _currentStage) {
                  supply = stages[_currentStage].tokenLimit.sub(
                      initialTokenSupply.sub(tokenSupply)
                  );
      
              
              } else if(_stageId > _currentStage) {
                  supply = stages[_stageId].tokenLimit.sub(stages[_stageId - 1].tokenLimit); 
              }
      
              return getEthAmountForTokensAtStage(
                  supply
              , _stageId);
          }
      
          
          function getEthAmountForTokensAtStage(uint256 _tokenAmount, uint8 _stageId) public view returns (uint256) {
              return _tokenAmount
              .mul(stages[_stageId].tokenPrice)
              .div(10 ** 18);
          }
      
          
          function getTokenAmountForEthAtStage(uint256 _ethAmount, uint8 _stageId) public view returns (uint256) {
              return _ethAmount
              .mul(10 ** 18)
              .div(stages[_stageId].tokenPrice);
          }
      
          
          function getCurrentBlockNumber() public view returns (uint256) {
              return uint256(block.number);
          }
      
          
          function getCurrentEffectiveBlockNumber() public view returns (uint256) {
              return uint256(block.number)
              .sub(frozenPeriod); 
          }
      
          
          function calcUnlockedAmount(uint256 _amount, uint256 _lastBlock) public view returns (uint256) {
      
              uint256 currentBlock = getCurrentEffectiveBlockNumber();
      
              if(_amount == 0) {
                  return 0;
              }
      
              
              if (currentBlock >= buyPhaseStartBlock && currentBlock < buyPhaseEndBlock) {
      
                  
                  uint256 lastBlock = _lastBlock;
                  if(lastBlock < buyPhaseStartBlock) {
                      lastBlock = buyPhaseStartBlock.sub(1); 
                  }
      
                  
                  uint256 passedBlocks = currentBlock.sub(lastBlock);
      
                  
                  uint256 totalBlockCount = buyPhaseEndBlock.sub(lastBlock);
      
                  return _amount.mul(
                      passedBlocks.mul(10 ** 20)
                      .div(totalBlockCount)
                  ).div(10 ** 20);
      
                  
              } else if (currentBlock >= buyPhaseEndBlock) {
                  return _amount;
              }
              
              return 0;
          }
      
          
      
      
          
          function sanityCheckProject() internal view {
              
              require(
                  committedETH == _projectCurrentlyReservedETH.add(_projectUnlockedETH),
                  'Project Sanity check failed! Reserved + Unlock must equal committedETH'
              );
      
              
              require(
                  address(this).balance == _projectUnlockedETH.add(_projectCurrentlyReservedETH).add(pendingETH).sub(projectWithdrawnETH),
                  'Project sanity check failed! balance = Unlock + Reserved - Withdrawn'
              );
          }
      
          
          function sanityCheckParticipant(address _participantAddress) internal view {
              Participant storage participantStats = participants[_participantAddress];
      
              
              require(
                  participantStats.reservedTokens == participantStats._currentReservedTokens.add(participantStats._unlockedTokens),
                  'Participant Sanity check failed! Reser. + Unlock must equal totalReser'
              );
          }
      
          
          function calcProjectAllocation() internal {
      
              uint256 newlyUnlockedEth = calcUnlockedAmount(_projectCurrentlyReservedETH, _projectLastBlock);
      
              
              _projectCurrentlyReservedETH = _projectCurrentlyReservedETH.sub(newlyUnlockedEth);
              _projectUnlockedETH = _projectUnlockedETH.add(newlyUnlockedEth);
              _projectLastBlock = getCurrentEffectiveBlockNumber();
      
              sanityCheckProject();
          }
      
          
          function calcParticipantAllocation(address _participantAddress) internal {
              Participant storage participantStats = participants[_participantAddress];
      
              
              participantStats._unlockedTokens = getParticipantUnlockedTokens(_participantAddress);
              participantStats._currentReservedTokens = getParticipantReservedTokens(_participantAddress);
      
              
              participantStats._lastBlock = getCurrentEffectiveBlockNumber();
      
              
              calcProjectAllocation();
          }
      
          
          function cancelPendingContributions(address _participantAddress, uint256 _sentValue)
          internal
          isInitialized
          isNotFrozen
          {
              Participant storage participantStats = participants[_participantAddress];
              uint256 participantPendingEth = participantStats.pendingETH;
      
              
              if(participantPendingEth == 0) {
                  
                  if(_sentValue > 0) {
                      address(uint160(_participantAddress)).transfer(_sentValue);
                  }
                  return;
              }
      
              
              for (uint8 stageId = 0; stageId <= stageCount; stageId++) {
                  participantStats.stages[stageId].pendingETH = 0;
              }
      
              
              participantStats.pendingETH = 0;
      
              
              canceledETH = canceledETH.add(participantPendingEth);
              pendingETH = pendingETH.sub(participantPendingEth);
      
              
              emit PendingContributionsCanceled(_participantAddress, participantPendingEth, uint32(participantStats.contributions));
              emit TransferEvent(
                  uint8(TransferTypes.CONTRIBUTION_CANCELED),
                  _participantAddress,
                  participantPendingEth
              );
      
      
              
              address(uint160(_participantAddress)).transfer(participantPendingEth.add(_sentValue));
      
              
              sanityCheckParticipant(_participantAddress);
              sanityCheckProject();
          }
      
      
          
          function acceptContributions(address _participantAddress)
          internal
          isInitialized
          isNotFrozen
          isRunning
          {
              Participant storage participantStats = participants[_participantAddress];
      
              
              if (participantStats.pendingETH == 0) {
                  return;
              }
      
              uint8 currentStage = getCurrentStage();
              uint256 totalRefundedETH;
              uint256 totalNewReservedTokens;
      
              calcParticipantAllocation(_participantAddress);
      
              
              if(participantStats.committedETH == 0) {
                  participantStats.firstContributionBlock = participantStats._lastBlock; 
              }
      
              
              for (uint8 stageId = 0; stageId <= stageCount; stageId++) {
                  ParticipantStageDetails storage byStage = participantStats.stages[stageId];
      
                  
                  if (byStage.pendingETH == 0) {
                      continue;
                  }
      
                  
                  if(stageId < currentStage) {
                      
                      participantStats.stages[currentStage].pendingETH = participantStats.stages[currentStage].pendingETH.add(byStage.pendingETH);
                      
                      byStage.pendingETH = 0;
                      continue;
                  }
      
                  
      
                  uint256 maxCommittableEth = committableEthAtStage(stageId, currentStage);
                  uint256 newlyCommittableEth = byStage.pendingETH;
                  uint256 returnEth = 0;
                  uint256 overflowEth = 0;
      
                  
                  
                  if (newlyCommittableEth > maxCommittableEth) {
                      overflowEth = newlyCommittableEth.sub(maxCommittableEth);
                      newlyCommittableEth = maxCommittableEth;
      
                      
                      if (stageId == stageCount) {
                          returnEth = overflowEth;
                          totalRefundedETH = totalRefundedETH.add(returnEth);
      
                      
                      } else {
                          participantStats.stages[stageId + 1].pendingETH = participantStats.stages[stageId + 1].pendingETH.add(overflowEth);
                          byStage.pendingETH = byStage.pendingETH.sub(overflowEth);
                      }
                  }
      
                  
                  uint256 newTokenAmount = getTokenAmountForEthAtStage(
                      newlyCommittableEth, stageId
                  );
      
                  totalNewReservedTokens = totalNewReservedTokens.add(newTokenAmount);
      
                  
                  participantStats._currentReservedTokens = participantStats._currentReservedTokens.add(newTokenAmount);
                  participantStats.reservedTokens = participantStats.reservedTokens.add(newTokenAmount);
                  participantStats.committedETH = participantStats.committedETH.add(newlyCommittableEth);
                  participantStats.pendingETH = participantStats.pendingETH.sub(newlyCommittableEth).sub(returnEth);
      
                  byStage.pendingETH = byStage.pendingETH.sub(newlyCommittableEth).sub(returnEth);
      
                  
                  tokenSupply = tokenSupply.sub(newTokenAmount);
                  pendingETH = pendingETH.sub(newlyCommittableEth).sub(returnEth);
                  committedETH = committedETH.add(newlyCommittableEth);
                  _projectCurrentlyReservedETH = _projectCurrentlyReservedETH.add(newlyCommittableEth);
      
                  
                  emit ContributionsAccepted(_participantAddress, newlyCommittableEth, newTokenAmount, stageId);
              }
      
              
              if (totalRefundedETH > 0) {
                  emit TransferEvent(uint8(TransferTypes.CONTRIBUTION_ACCEPTED_OVERFLOW), _participantAddress, totalRefundedETH);
                  address(uint160(_participantAddress)).transfer(totalRefundedETH);
              }
      
              
              
              IERC777(tokenAddress).send(_participantAddress, totalNewReservedTokens, "");
      
              
              sanityCheckParticipant(_participantAddress);
              sanityCheckProject();
          }
      
      
          
          function withdraw(address _participantAddress, uint256 _returnedTokenAmount)
          internal
          isInitialized
          isNotFrozen
          isRunning
          {
              Participant storage participantStats = participants[_participantAddress];
      
              calcParticipantAllocation(_participantAddress);
      
              require(_returnedTokenAmount > 0, 'You can not withdraw without sending tokens.');
              require(participantStats._currentReservedTokens > 0 && participantStats.reservedTokens > 0, 'You can not withdraw, you have no locked tokens.');
      
              uint256 returnedTokenAmount = _returnedTokenAmount;
              uint256 overflowingTokenAmount;
              uint256 returnEthAmount;
      
              
              if (returnedTokenAmount > participantStats._currentReservedTokens) {
                  overflowingTokenAmount = returnedTokenAmount.sub(participantStats._currentReservedTokens);
                  returnedTokenAmount = participantStats._currentReservedTokens;
              }
      
              
              returnEthAmount = participantStats.committedETH.mul(
                  returnedTokenAmount.sub(1).mul(10 ** 20) 
                  .div(participantStats.reservedTokens)
              ).div(10 ** 20);
      
      
              
              participantStats.withdraws++;
              participantStats._currentReservedTokens = participantStats._currentReservedTokens.sub(returnedTokenAmount);
              participantStats.reservedTokens = participantStats.reservedTokens.sub(returnedTokenAmount);
              participantStats.committedETH = participantStats.committedETH.sub(returnEthAmount);
      
              
              tokenSupply = tokenSupply.add(returnedTokenAmount);
              withdrawnETH = withdrawnETH.add(returnEthAmount);
              committedETH = committedETH.sub(returnEthAmount);
      
              _projectCurrentlyReservedETH = _projectCurrentlyReservedETH.sub(returnEthAmount);
      
      
              
              if (overflowingTokenAmount > 0) {
                  
                  bytes memory data;
      
                  
                  emit TransferEvent(uint8(TransferTypes.PARTICIPANT_WITHDRAW_OVERFLOW), _participantAddress, overflowingTokenAmount);
      
                  
                  IERC777(tokenAddress).send(_participantAddress, overflowingTokenAmount, data);
              }
      
              
              emit ParticipantWithdraw(_participantAddress, returnEthAmount, returnedTokenAmount, uint32(participantStats.withdraws));
              emit TransferEvent(uint8(TransferTypes.PARTICIPANT_WITHDRAW), _participantAddress, returnEthAmount);
      
              
              address(uint160(_participantAddress)).transfer(returnEthAmount);
      
              
              sanityCheckParticipant(_participantAddress);
              sanityCheckProject();
          }
      
          
      
          
          modifier onlyProjectAddress() {
              require(msg.sender == projectAddress, "Only the project can call this method.");
              _;
          }
      
          
          modifier onlyDeployingAddress() {
              require(msg.sender == deployingAddress, "Only the deployer can call this method.");
              _;
          }
      
          
          modifier onlyWhitelistingAddress() {
              require(msg.sender == whitelistingAddress, "Only the whitelist controller can call this method.");
              _;
          }
      
          
          modifier onlyFreezerAddress() {
              require(msg.sender == freezerAddress, "Only the freezer address can call this method.");
              _;
          }
      
          
          modifier onlyRescuerAddress() {
              require(msg.sender == rescuerAddress, "Only the rescuer address can call this method.");
              _;
          }
      
          
          modifier isInitialized() {
              require(initialized == true, "Contract must be initialized.");
              _;
          }
      
          
          modifier isNotInitialized() {
              require(initialized == false, "Contract can not be initialized.");
              _;
          }
      
          
          modifier isFrozen() {
              require(frozen == true, "rICO has to be frozen!");
              _;
          }
      
          
          modifier isNotFrozen() {
              require(frozen == false, "rICO is frozen!");
              _;
          }
      
          
          modifier isRunning() {
              uint256 blockNumber = getCurrentEffectiveBlockNumber();
              require(blockNumber >= commitPhaseStartBlock && blockNumber <= buyPhaseEndBlock, "Current block is outside the rICO period.");
              _;
          }
      }

      File 2 of 3: ReversibleICOToken
      /**
       *Submitted for verification at Etherscan.io on 2020-05-13
      */
      /*
       * source        https://github.com/lukso-network/rICO-smart-contracts
       * @name       LUKSO Token
       * @author      Micky Socaci <[email protected]>, Fabian Vogelsteller <@frozeman>
       * @license     Apachae 2.0
       */
      
      /**
       * @dev Implementation of the `IERC777` interface.
       *
       * This implementation is agnostic to the way tokens are created. This means
       * that a supply mechanism has to be added in a derived contract using `_mint`.
       *
       * Support for ERC20 is included in this contract, as specified by the EIP: both
       * the ERC777 and ERC20 interfaces can be safely used when interacting with it.
       * Both `IERC777.Sent` and `IERC20.Transfer` events are emitted on token
       * movements.
       *
       * Additionally, the `granularity` value is hard-coded to `1`, meaning that there
       * are no special restrictions in the amount of tokens that created, moved, or
       * destroyed. This makes integration with ERC20 applications seamless.
       */
      
      pragma solidity ^0.5.0;
      
      
      interface IERC777 {
          
          function name() external view returns (string memory);
      
          
          function symbol() external view returns (string memory);
      
          
          function granularity() external view returns (uint256);
      
          
          function totalSupply() external view returns (uint256);
      
          
          function balanceOf(address owner) external view returns (uint256);
      
          
          function send(address recipient, uint256 amount, bytes calldata data) external;
      
          
          function burn(uint256 amount, bytes calldata data) external;
      
          
          function isOperatorFor(address operator, address tokenHolder) external view returns (bool);
      
          
          function authorizeOperator(address operator) external;
      
          
          function revokeOperator(address operator) external;
      
          
          function defaultOperators() external view returns (address[] memory);
      
          
          function operatorSend(
              address sender,
              address recipient,
              uint256 amount,
              bytes calldata data,
              bytes calldata operatorData
          ) external;
      
          
          function operatorBurn(
              address account,
              uint256 amount,
              bytes calldata data,
              bytes calldata operatorData
          ) external;
      
          event Sent(
              address indexed operator,
              address indexed from,
              address indexed to,
              uint256 amount,
              bytes data,
              bytes operatorData
          );
      
          event Minted(address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData);
      
          event Burned(address indexed operator, address indexed from, uint256 amount, bytes data, bytes operatorData);
      
          event AuthorizedOperator(address indexed operator, address indexed tokenHolder);
      
          event RevokedOperator(address indexed operator, address indexed tokenHolder);
      }
      
      interface IERC777Recipient {
          
          function tokensReceived(
              address operator,
              address from,
              address to,
              uint256 amount,
              bytes calldata userData,
              bytes calldata operatorData
          ) external;
      }
      
      interface IERC777Sender {
          
          function tokensToSend(
              address operator,
              address from,
              address to,
              uint256 amount,
              bytes calldata userData,
              bytes calldata operatorData
          ) external;
      }
      
      interface IERC20 {
          
          function totalSupply() external view returns (uint256);
      
          
          function balanceOf(address account) external view returns (uint256);
      
          
          function transfer(address recipient, uint256 amount) external returns (bool);
      
          
          function allowance(address owner, address spender) external view returns (uint256);
      
          
          function approve(address spender, uint256 amount) external returns (bool);
      
          
          function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
      
          
          event Transfer(address indexed from, address indexed to, uint256 value);
      
          
          event Approval(address indexed owner, address indexed spender, uint256 value);
      }
      
      library SafeMath {
          
          function add(uint256 a, uint256 b) internal pure returns (uint256) {
              uint256 c = a + b;
              require(c >= a, "SafeMath: addition overflow");
      
              return c;
          }
      
          
          function sub(uint256 a, uint256 b) internal pure returns (uint256) {
              require(b <= a, "SafeMath: subtraction overflow");
              uint256 c = a - b;
      
              return c;
          }
      
          
          function mul(uint256 a, uint256 b) internal pure returns (uint256) {
              
              
              
              if (a == 0) {
                  return 0;
              }
      
              uint256 c = a * b;
              require(c / a == b, "SafeMath: multiplication overflow");
      
              return c;
          }
      
          
          function div(uint256 a, uint256 b) internal pure returns (uint256) {
              
              require(b > 0, "SafeMath: division by zero");
              uint256 c = a / b;
              
      
              return c;
          }
      
          
          function mod(uint256 a, uint256 b) internal pure returns (uint256) {
              require(b != 0, "SafeMath: modulo by zero");
              return a % b;
          }
      }
      
      library Address {
          
          function isContract(address account) internal view returns (bool) {
              
              
              
              
              
              
              
              bytes32 codehash;
              bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
              
              assembly { codehash := extcodehash(account) }
              return (codehash != 0x0 && codehash != accountHash);
          }
      
          
          function toPayable(address account) internal pure returns (address payable) {
              return address(uint160(account));
          }
      }
      
      interface IERC1820Registry {
          
          function setManager(address account, address newManager) external;
      
          
          function getManager(address account) external view returns (address);
      
          
          function setInterfaceImplementer(address account, bytes32 interfaceHash, address implementer) external;
      
          
          function getInterfaceImplementer(address account, bytes32 interfaceHash) external view returns (address);
      
          
          function interfaceHash(string calldata interfaceName) external pure returns (bytes32);
      
          
          function updateERC165Cache(address account, bytes4 interfaceId) external;
      
          
          function implementsERC165Interface(address account, bytes4 interfaceId) external view returns (bool);
      
          
          function implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool);
      
          event InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer);
      
          event ManagerChanged(address indexed account, address indexed newManager);
      }
      
      contract ERC777 is IERC777, IERC20 {
          using SafeMath for uint256;
          using Address for address;
      
          IERC1820Registry private _erc1820 = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
      
          mapping(address => uint256) private _balances;
      
          uint256 private _totalSupply;
      
          string private _name;
          string private _symbol;
      
          
          
      
          
          bytes32 constant private TOKENS_SENDER_INTERFACE_HASH =
              0x29ddb589b1fb5fc7cf394961c1adf5f8c6454761adf795e67fe149f658abe895;
      
          
          bytes32 constant private TOKENS_RECIPIENT_INTERFACE_HASH =
              0xb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b;
      
          
          address[] private _defaultOperatorsArray;
      
          
          mapping(address => bool) private _defaultOperators;
      
          
          mapping(address => mapping(address => bool)) private _operators;
          mapping(address => mapping(address => bool)) private _revokedDefaultOperators;
      
          
          mapping (address => mapping (address => uint256)) private _allowances;
      
          
          constructor(
              string memory name,
              string memory symbol,
              address[] memory defaultOperators
          ) public {
              _name = name;
              _symbol = symbol;
      
              _defaultOperatorsArray = defaultOperators;
              for (uint256 i = 0; i < _defaultOperatorsArray.length; i++) {
                  _defaultOperators[_defaultOperatorsArray[i]] = true;
              }
      
              
              _erc1820.setInterfaceImplementer(address(this), keccak256("ERC777Token"), address(this));
              _erc1820.setInterfaceImplementer(address(this), keccak256("ERC20Token"), address(this));
          }
      
          
          function name() public view returns (string memory) {
              return _name;
          }
      
          
          function symbol() public view returns (string memory) {
              return _symbol;
          }
      
          
          function decimals() public pure returns (uint8) {
              return 18;
          }
      
          
          function granularity() public view returns (uint256) {
              return 1;
          }
      
          
          function totalSupply() public view returns (uint256) {
              return _totalSupply;
          }
      
          
          function balanceOf(address tokenHolder) public view returns (uint256) {
              return _balances[tokenHolder];
          }
      
          
          function send(address recipient, uint256 amount, bytes calldata data) external {
              _send(msg.sender, msg.sender, recipient, amount, data, "", true);
          }
      
          
          function transfer(address recipient, uint256 amount) external returns (bool) {
              require(recipient != address(0), "ERC777: transfer to the zero address");
      
              address from = msg.sender;
      
              _callTokensToSend(from, from, recipient, amount, "", "");
      
              _move(from, from, recipient, amount, "", "");
      
              _callTokensReceived(from, from, recipient, amount, "", "", false);
      
              return true;
          }
      
          
          function burn(uint256 amount, bytes calldata data) external {
              _burn(msg.sender, msg.sender, amount, data, "");
          }
      
          
          function isOperatorFor(
              address operator,
              address tokenHolder
          ) public view returns (bool) {
              return operator == tokenHolder ||
                  (_defaultOperators[operator] && !_revokedDefaultOperators[tokenHolder][operator]) ||
                  _operators[tokenHolder][operator];
          }
      
          
          function authorizeOperator(address operator) external {
              require(msg.sender != operator, "ERC777: authorizing self as operator");
      
              if (_defaultOperators[operator]) {
                  delete _revokedDefaultOperators[msg.sender][operator];
              } else {
                  _operators[msg.sender][operator] = true;
              }
      
              emit AuthorizedOperator(operator, msg.sender);
          }
      
          
          function revokeOperator(address operator) external {
              require(operator != msg.sender, "ERC777: revoking self as operator");
      
              if (_defaultOperators[operator]) {
                  _revokedDefaultOperators[msg.sender][operator] = true;
              } else {
                  delete _operators[msg.sender][operator];
              }
      
              emit RevokedOperator(operator, msg.sender);
          }
      
          
          function defaultOperators() public view returns (address[] memory) {
              return _defaultOperatorsArray;
          }
      
          
          function operatorSend(
              address sender,
              address recipient,
              uint256 amount,
              bytes calldata data,
              bytes calldata operatorData
          )
          external
          {
              require(isOperatorFor(msg.sender, sender), "ERC777: caller is not an operator for holder");
              _send(msg.sender, sender, recipient, amount, data, operatorData, true);
          }
      
          
          function operatorBurn(address account, uint256 amount, bytes calldata data, bytes calldata operatorData) external {
              require(isOperatorFor(msg.sender, account), "ERC777: caller is not an operator for holder");
              _burn(msg.sender, account, amount, data, operatorData);
          }
      
          
          function allowance(address holder, address spender) public view returns (uint256) {
              return _allowances[holder][spender];
          }
      
          
          function approve(address spender, uint256 value) external returns (bool) {
              address holder = msg.sender;
              _approve(holder, spender, value);
              return true;
          }
      
         
          function transferFrom(address holder, address recipient, uint256 amount) external returns (bool) {
              require(recipient != address(0), "ERC777: transfer to the zero address");
              require(holder != address(0), "ERC777: transfer from the zero address");
      
              address spender = msg.sender;
      
              _callTokensToSend(spender, holder, recipient, amount, "", "");
      
              _move(spender, holder, recipient, amount, "", "");
              _approve(holder, spender, _allowances[holder][spender].sub(amount));
      
              _callTokensReceived(spender, holder, recipient, amount, "", "", false);
      
              return true;
          }
      
          
          function _mint(
              address operator,
              address account,
              uint256 amount,
              bytes memory userData,
              bytes memory operatorData
          )
          internal
          {
              require(account != address(0), "ERC777: mint to the zero address");
      
              
              _totalSupply = _totalSupply.add(amount);
              _balances[account] = _balances[account].add(amount);
      
              _callTokensReceived(operator, address(0), account, amount, userData, operatorData, true);
      
              emit Minted(operator, account, amount, userData, operatorData);
              emit Transfer(address(0), account, amount);
          }
      
          
          function _send(
              address operator,
              address from,
              address to,
              uint256 amount,
              bytes memory userData,
              bytes memory operatorData,
              bool requireReceptionAck
          )
              private
          {
              require(from != address(0), "ERC777: send from the zero address");
              require(to != address(0), "ERC777: send to the zero address");
      
              _callTokensToSend(operator, from, to, amount, userData, operatorData);
      
              _move(operator, from, to, amount, userData, operatorData);
      
              _callTokensReceived(operator, from, to, amount, userData, operatorData, requireReceptionAck);
          }
      
          
          function _burn(
              address operator,
              address from,
              uint256 amount,
              bytes memory data,
              bytes memory operatorData
          )
              internal
          {
              require(from != address(0), "ERC777: burn from the zero address");
      
              _callTokensToSend(operator, from, address(0), amount, data, operatorData);
      
              
              _totalSupply = _totalSupply.sub(amount);
              _balances[from] = _balances[from].sub(amount);
      
              emit Burned(operator, from, amount, data, operatorData);
              emit Transfer(from, address(0), amount);
          }
      
          function _move(
              address operator,
              address from,
              address to,
              uint256 amount,
              bytes memory userData,
              bytes memory operatorData
          )
              internal
          {
              _balances[from] = _balances[from].sub(amount);
              _balances[to] = _balances[to].add(amount);
      
              emit Sent(operator, from, to, amount, userData, operatorData);
              emit Transfer(from, to, amount);
          }
      
          function _approve(address holder, address spender, uint256 value) private {
              
              
              
              require(spender != address(0), "ERC777: approve to the zero address");
      
              _allowances[holder][spender] = value;
              emit Approval(holder, spender, value);
          }
      
          
          function _callTokensToSend(
              address operator,
              address from,
              address to,
              uint256 amount,
              bytes memory userData,
              bytes memory operatorData
          )
              private
          {
              address implementer = _erc1820.getInterfaceImplementer(from, TOKENS_SENDER_INTERFACE_HASH);
              if (implementer != address(0)) {
                  IERC777Sender(implementer).tokensToSend(operator, from, to, amount, userData, operatorData);
              }
          }
      
          
          function _callTokensReceived(
              address operator,
              address from,
              address to,
              uint256 amount,
              bytes memory userData,
              bytes memory operatorData,
              bool requireReceptionAck
          )
              private
          {
              address implementer = _erc1820.getInterfaceImplementer(to, TOKENS_RECIPIENT_INTERFACE_HASH);
              if (implementer != address(0)) {
                  IERC777Recipient(implementer).tokensReceived(operator, from, to, amount, userData, operatorData);
              } else if (requireReceptionAck) {
                  require(!to.isContract(), "ERC777: token recipient contract has no implementer for ERC777TokensRecipient");
              }
          }
      }
      
      interface ReversibleICO {
          function getParticipantReservedTokens(address) external view returns (uint256);
      }
      
      contract ReversibleICOToken is ERC777 {
      
          ReversibleICO public rICO;
      
          bool public frozen; 
          bool public initialized; 
          
          
          address public deployingAddress;
          address public tokenGenesisAddress; 
          address public migrationAddress; 
          address public freezerAddress; 
          address public rescuerAddress; 
      
          
          event SetRICOaddress(address indexed rICOAddress);
          event SetMigrationAddress(address indexed migrationAddress);
          event Frozen(address indexed freezerAddress);
          event Unfrozen(address indexed freezerAddress);
          event RemovedFreezer(address indexed freezerAddress);
          event ChangedRICO(address indexed rICOAddress, address indexed rescuerAddress);
      
      
          
      
          constructor(
              string memory name,
              string memory symbol,
              address[] memory _defaultOperators
          )
          ERC777(name, symbol, _defaultOperators)
          public
          {
              deployingAddress = msg.sender;
          }
      
          
          function init(
              address _ricoAddress,
              address _freezerAddress,
              address _rescuerAddress,
              address _tokenGenesisAddress,
              uint256 _initialSupply
          )
          public
          isNotInitialized
          onlyDeployingAddress
          {
              require(_freezerAddress != address(0), "_freezerAddress cannot be 0x");
              require(_rescuerAddress != address(0), "_rescuerAddress cannot be 0x");
              require(_tokenGenesisAddress != address(0), "_tokenGenesisAddress cannot be 0x");
      
              tokenGenesisAddress = _tokenGenesisAddress;
              freezerAddress = _freezerAddress;
              rescuerAddress = _rescuerAddress;
      
              _mint(_tokenGenesisAddress, _tokenGenesisAddress, _initialSupply, "", "");
      
              if(_ricoAddress != address(0)) {
                  rICO = ReversibleICO(_ricoAddress);
                  emit SetRICOaddress(_ricoAddress);
              }
      
              initialized = true;
          }
      
          function setRICOaddress(address _ricoAddress)
          public
          onlyTokenGenesisAddress
          {
              require(address(rICO) == address(0), "rICO address already set!");
              require(_ricoAddress != address(0), "rICO address cannot be 0x.");
      
              rICO = ReversibleICO(_ricoAddress);
              emit SetRICOaddress(_ricoAddress);
          }
      
          
          function setMigrationAddress(address _migrationAddress)
          public
          onlyTokenGenesisAddress
          {
              migrationAddress = _migrationAddress;
              emit SetMigrationAddress(migrationAddress);
          }
      
      
          
          function removeFreezer()
          public
          onlyFreezerAddress
          isNotFrozen
          {
              freezerAddress = address(0);
              emit RemovedFreezer(freezerAddress);
          }
      
          function freeze() public onlyFreezerAddress {
              frozen = true;
              emit Frozen(freezerAddress);
          }
      
          function unfreeze() public onlyFreezerAddress {
              frozen = false;
              emit Unfrozen(freezerAddress);
          }
      
          
          function changeRICO(address _newRicoAddress)
          public
          onlyRescuerAddress
          isFrozen
          {
              rICO = ReversibleICO(_newRicoAddress);
              emit ChangedRICO(_newRicoAddress, rescuerAddress);
          }
      
          
          function getLockedBalance(address _owner) public view returns(uint256) {
              
              if(address(rICO) != address(0)) {
                  return rICO.getParticipantReservedTokens(_owner);
              } else {
                  return 0;
              }
          }
      
          function getUnlockedBalance(address _owner) public view returns(uint256) {
              uint256 balance = balanceOf(_owner);
      
              
              if(address(rICO) != address(0)) {
                  uint256 locked = rICO.getParticipantReservedTokens(_owner);
      
                  if(balance > 0 && locked > 0) {
                      if(balance >= locked) {
                          return balance.sub(locked);
                      } else {
                          return 0;
                      }
                  }
              }
      
              return balance;
          }
      
      
          
      
          
          
          function _move(
              address _operator,
              address _from,
              address _to,
              uint256 _amount,
              bytes memory _userData,
              bytes memory _operatorData
          )
          internal
          isNotFrozen
          isInitialized
          {
      
              
              if(
                  _to == address(rICO) ||
                  _to == migrationAddress
              ) {
                  
                  require(_amount <= balanceOf(_from), "Sending failed: Insufficient funds");
      
              } else {
                  
                  require(_amount <= getUnlockedBalance(_from), "Sending failed: Insufficient funds");
              }
      
              ERC777._move(_operator, _from, _to, _amount, _userData, _operatorData);
          }
      
          
          function _burn(
              address _operator,
              address _from,
              uint256 _amount,
              bytes memory _data,
              bytes memory _operatorData
          )
          internal
          isNotFrozen
          isInitialized
          {
              require(_amount <= getUnlockedBalance(_from), "Burning failed: Insufficient funds");
              ERC777._burn(_operator, _from, _amount, _data, _operatorData);
          }
      
      
          
          
          modifier onlyDeployingAddress() {
              require(msg.sender == deployingAddress, "Only the deployer can call this method.");
              _;
          }
      
          
          modifier onlyTokenGenesisAddress() {
              require(msg.sender == tokenGenesisAddress, "Only the tokenGenesisAddress can call this method.");
              _;
          }
      
          
          modifier onlyFreezerAddress() {
              require(msg.sender == freezerAddress, "Only the freezer address can call this method.");
              _;
          }
      
          
          modifier onlyRescuerAddress() {
              require(msg.sender == rescuerAddress, "Only the rescuer address can call this method.");
              _;
          }
      
          
          modifier isInitialized() {
              require(initialized == true, "Contract must be initialized.");
              _;
          }
      
          
          modifier isNotInitialized() {
              require(initialized == false, "Contract is already initialized.");
              _;
          }
      
          
          modifier isFrozen() {
              require(frozen == true, "Token contract not frozen.");
              _;
          }
      
          
          modifier isNotFrozen() {
              require(frozen == false, "Token contract is frozen!");
              _;
          }
      }

      File 3 of 3: ERC1820Registry
      /* ERC1820 Pseudo-introspection Registry Contract
       * This standard defines a universal registry smart contract where any address (contract or regular account) can
       * register which interface it supports and which smart contract is responsible for its implementation.
       *
       * Written in 2019 by Jordi Baylina and Jacques Dafflon
       *
       * To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to
       * this software to the public domain worldwide. This software is distributed without any warranty.
       *
       * You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see
       * <http://creativecommons.org/publicdomain/zero/1.0/>.
       *
       *    ███████╗██████╗  ██████╗ ██╗ █████╗ ██████╗  ██████╗
       *    ██╔════╝██╔══██╗██╔════╝███║██╔══██╗╚════██╗██╔═████╗
       *    █████╗  ██████╔╝██║     ╚██║╚█████╔╝ █████╔╝██║██╔██║
       *    ██╔══╝  ██╔══██╗██║      ██║██╔══██╗██╔═══╝ ████╔╝██║
       *    ███████╗██║  ██║╚██████╗ ██║╚█████╔╝███████╗╚██████╔╝
       *    ╚══════╝╚═╝  ╚═╝ ╚═════╝ ╚═╝ ╚════╝ ╚══════╝ ╚═════╝
       *
       *    ██████╗ ███████╗ ██████╗ ██╗███████╗████████╗██████╗ ██╗   ██╗
       *    ██╔══██╗██╔════╝██╔════╝ ██║██╔════╝╚══██╔══╝██╔══██╗╚██╗ ██╔╝
       *    ██████╔╝█████╗  ██║  ███╗██║███████╗   ██║   ██████╔╝ ╚████╔╝
       *    ██╔══██╗██╔══╝  ██║   ██║██║╚════██║   ██║   ██╔══██╗  ╚██╔╝
       *    ██║  ██║███████╗╚██████╔╝██║███████║   ██║   ██║  ██║   ██║
       *    ╚═╝  ╚═╝╚══════╝ ╚═════╝ ╚═╝╚══════╝   ╚═╝   ╚═╝  ╚═╝   ╚═╝
       *
       */
      pragma solidity 0.5.3;
      // IV is value needed to have a vanity address starting with '0x1820'.
      // IV: 53759
      
      /// @dev The interface a contract MUST implement if it is the implementer of
      /// some (other) interface for any address other than itself.
      interface ERC1820ImplementerInterface {
          /// @notice Indicates whether the contract implements the interface 'interfaceHash' for the address 'addr' or not.
          /// @param interfaceHash keccak256 hash of the name of the interface
          /// @param addr Address for which the contract will implement the interface
          /// @return ERC1820_ACCEPT_MAGIC only if the contract implements 'interfaceHash' for the address 'addr'.
          function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32);
      }
      
      
      /// @title ERC1820 Pseudo-introspection Registry Contract
      /// @author Jordi Baylina and Jacques Dafflon
      /// @notice This contract is the official implementation of the ERC1820 Registry.
      /// @notice For more details, see https://eips.ethereum.org/EIPS/eip-1820
      contract ERC1820Registry {
          /// @notice ERC165 Invalid ID.
          bytes4 constant internal INVALID_ID = 0xffffffff;
          /// @notice Method ID for the ERC165 supportsInterface method (= `bytes4(keccak256('supportsInterface(bytes4)'))`).
          bytes4 constant internal ERC165ID = 0x01ffc9a7;
          /// @notice Magic value which is returned if a contract implements an interface on behalf of some other address.
          bytes32 constant internal ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC"));
      
          /// @notice mapping from addresses and interface hashes to their implementers.
          mapping(address => mapping(bytes32 => address)) internal interfaces;
          /// @notice mapping from addresses to their manager.
          mapping(address => address) internal managers;
          /// @notice flag for each address and erc165 interface to indicate if it is cached.
          mapping(address => mapping(bytes4 => bool)) internal erc165Cached;
      
          /// @notice Indicates a contract is the 'implementer' of 'interfaceHash' for 'addr'.
          event InterfaceImplementerSet(address indexed addr, bytes32 indexed interfaceHash, address indexed implementer);
          /// @notice Indicates 'newManager' is the address of the new manager for 'addr'.
          event ManagerChanged(address indexed addr, address indexed newManager);
      
          /// @notice Query if an address implements an interface and through which contract.
          /// @param _addr Address being queried for the implementer of an interface.
          /// (If '_addr' is the zero address then 'msg.sender' is assumed.)
          /// @param _interfaceHash Keccak256 hash of the name of the interface as a string.
          /// E.g., 'web3.utils.keccak256("ERC777TokensRecipient")' for the 'ERC777TokensRecipient' interface.
          /// @return The address of the contract which implements the interface '_interfaceHash' for '_addr'
          /// or '0' if '_addr' did not register an implementer for this interface.
          function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address) {
              address addr = _addr == address(0) ? msg.sender : _addr;
              if (isERC165Interface(_interfaceHash)) {
                  bytes4 erc165InterfaceHash = bytes4(_interfaceHash);
                  return implementsERC165Interface(addr, erc165InterfaceHash) ? addr : address(0);
              }
              return interfaces[addr][_interfaceHash];
          }
      
          /// @notice Sets the contract which implements a specific interface for an address.
          /// Only the manager defined for that address can set it.
          /// (Each address is the manager for itself until it sets a new manager.)
          /// @param _addr Address for which to set the interface.
          /// (If '_addr' is the zero address then 'msg.sender' is assumed.)
          /// @param _interfaceHash Keccak256 hash of the name of the interface as a string.
          /// E.g., 'web3.utils.keccak256("ERC777TokensRecipient")' for the 'ERC777TokensRecipient' interface.
          /// @param _implementer Contract address implementing '_interfaceHash' for '_addr'.
          function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external {
              address addr = _addr == address(0) ? msg.sender : _addr;
              require(getManager(addr) == msg.sender, "Not the manager");
      
              require(!isERC165Interface(_interfaceHash), "Must not be an ERC165 hash");
              if (_implementer != address(0) && _implementer != msg.sender) {
                  require(
                      ERC1820ImplementerInterface(_implementer)
                          .canImplementInterfaceForAddress(_interfaceHash, addr) == ERC1820_ACCEPT_MAGIC,
                      "Does not implement the interface"
                  );
              }
              interfaces[addr][_interfaceHash] = _implementer;
              emit InterfaceImplementerSet(addr, _interfaceHash, _implementer);
          }
      
          /// @notice Sets '_newManager' as manager for '_addr'.
          /// The new manager will be able to call 'setInterfaceImplementer' for '_addr'.
          /// @param _addr Address for which to set the new manager.
          /// @param _newManager Address of the new manager for 'addr'. (Pass '0x0' to reset the manager to '_addr'.)
          function setManager(address _addr, address _newManager) external {
              require(getManager(_addr) == msg.sender, "Not the manager");
              managers[_addr] = _newManager == _addr ? address(0) : _newManager;
              emit ManagerChanged(_addr, _newManager);
          }
      
          /// @notice Get the manager of an address.
          /// @param _addr Address for which to return the manager.
          /// @return Address of the manager for a given address.
          function getManager(address _addr) public view returns(address) {
              // By default the manager of an address is the same address
              if (managers[_addr] == address(0)) {
                  return _addr;
              } else {
                  return managers[_addr];
              }
          }
      
          /// @notice Compute the keccak256 hash of an interface given its name.
          /// @param _interfaceName Name of the interface.
          /// @return The keccak256 hash of an interface name.
          function interfaceHash(string calldata _interfaceName) external pure returns(bytes32) {
              return keccak256(abi.encodePacked(_interfaceName));
          }
      
          /* --- ERC165 Related Functions --- */
          /* --- Developed in collaboration with William Entriken. --- */
      
          /// @notice Updates the cache with whether the contract implements an ERC165 interface or not.
          /// @param _contract Address of the contract for which to update the cache.
          /// @param _interfaceId ERC165 interface for which to update the cache.
          function updateERC165Cache(address _contract, bytes4 _interfaceId) external {
              interfaces[_contract][_interfaceId] = implementsERC165InterfaceNoCache(
                  _contract, _interfaceId) ? _contract : address(0);
              erc165Cached[_contract][_interfaceId] = true;
          }
      
          /// @notice Checks whether a contract implements an ERC165 interface or not.
          //  If the result is not cached a direct lookup on the contract address is performed.
          //  If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling
          //  'updateERC165Cache' with the contract address.
          /// @param _contract Address of the contract to check.
          /// @param _interfaceId ERC165 interface to check.
          /// @return True if '_contract' implements '_interfaceId', false otherwise.
          function implementsERC165Interface(address _contract, bytes4 _interfaceId) public view returns (bool) {
              if (!erc165Cached[_contract][_interfaceId]) {
                  return implementsERC165InterfaceNoCache(_contract, _interfaceId);
              }
              return interfaces[_contract][_interfaceId] == _contract;
          }
      
          /// @notice Checks whether a contract implements an ERC165 interface or not without using nor updating the cache.
          /// @param _contract Address of the contract to check.
          /// @param _interfaceId ERC165 interface to check.
          /// @return True if '_contract' implements '_interfaceId', false otherwise.
          function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) public view returns (bool) {
              uint256 success;
              uint256 result;
      
              (success, result) = noThrowCall(_contract, ERC165ID);
              if (success == 0 || result == 0) {
                  return false;
              }
      
              (success, result) = noThrowCall(_contract, INVALID_ID);
              if (success == 0 || result != 0) {
                  return false;
              }
      
              (success, result) = noThrowCall(_contract, _interfaceId);
              if (success == 1 && result == 1) {
                  return true;
              }
              return false;
          }
      
          /// @notice Checks whether the hash is a ERC165 interface (ending with 28 zeroes) or not.
          /// @param _interfaceHash The hash to check.
          /// @return True if '_interfaceHash' is an ERC165 interface (ending with 28 zeroes), false otherwise.
          function isERC165Interface(bytes32 _interfaceHash) internal pure returns (bool) {
              return _interfaceHash & 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0;
          }
      
          /// @dev Make a call on a contract without throwing if the function does not exist.
          function noThrowCall(address _contract, bytes4 _interfaceId)
              internal view returns (uint256 success, uint256 result)
          {
              bytes4 erc165ID = ERC165ID;
      
              assembly {
                  let x := mload(0x40)               // Find empty storage location using "free memory pointer"
                  mstore(x, erc165ID)                // Place signature at beginning of empty storage
                  mstore(add(x, 0x04), _interfaceId) // Place first argument directly next to signature
      
                  success := staticcall(
                      30000,                         // 30k gas
                      _contract,                     // To addr
                      x,                             // Inputs are stored at location x
                      0x24,                          // Inputs are 36 (4 + 32) bytes long
                      x,                             // Store output over input (saves space)
                      0x20                           // Outputs are 32 bytes long
                  )
      
                  result := mload(x)                 // Load the result
              }
          }
      }