ETH Price: $2,117.58 (+6.98%)

Transaction Decoder

Block:
12632777 at Jun-14-2021 01:17:22 PM +UTC
Transaction Fee:
0.003021249 ETH $6.40
Gas Used:
183,106 Gas / 16.5 Gwei

Emitted Events:

188 FlexaCollateralManager.SupplyReceipt( supplier=[Sender] 0xd37c580ec0ee0f937590e24165f31cee0ad5021d, partition=CCCCCCCC2862B8CB21CAEDB8706D7F8B3445D8DFC790C524E3990EF014E7C578, amount=16780000000000000000000, nonce=6121 )
189 Amp.Transfer( from=[Sender] 0xd37c580ec0ee0f937590e24165f31cee0ad5021d, to=FlexaCollateralManager, value=16780000000000000000000 )
190 Amp.TransferByPartition( fromPartition=0000000000000000000000000000000000000000000000000000000000000000, operator=[Sender] 0xd37c580ec0ee0f937590e24165f31cee0ad5021d, from=[Sender] 0xd37c580ec0ee0f937590e24165f31cee0ad5021d, to=FlexaCollateralManager, value=16780000000000000000000, data=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCCCCCCCC2862B8CB21CAEDB8706D7F8B3445D8DFC790C524E3990EF014E7C578, operatorData=0x )
191 Amp.ChangedPartition( fromPartition=0000000000000000000000000000000000000000000000000000000000000000, toPartition=CCCCCCCC2862B8CB21CAEDB8706D7F8B3445D8DFC790C524E3990EF014E7C578, value=16780000000000000000000 )

Account State Difference:

  Address   Before After State Difference Code
(Crazy Pool)
46.49601614815312519 Eth46.49903739715312519 Eth0.003021249
0x706D7F8B...014e7C578
(Flexa: Staking)
0xd37C580E...e0ad5021d
0.0035600005 Eth
Nonce: 14
0.0005387515 Eth
Nonce: 15
0.003021249
0xfF208177...8D11095C2

Execution Trace

Amp.transferByPartition( _partition=0000000000000000000000000000000000000000000000000000000000000000, _from=0xd37C580Ec0Ee0f937590E24165F31CEe0ad5021d, _to=0x706D7F8B3445D8Dfc790C524E3990ef014e7C578, _value=16780000000000000000000, _data=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCCCCCCCC2862B8CB21CAEDB8706D7F8B3445D8DFC790C524E3990EF014E7C578, _operatorData=0x ) => ( CCCCCCCC2862B8CB21CAEDB8706D7F8B3445D8DFC790C524E3990EF014E7C578 )
  • ERC1820Registry.getInterfaceImplementer( _addr=0xd37C580Ec0Ee0f937590E24165F31CEe0ad5021d, _interfaceHash=60881B58A7AD1EBD3BC0E92B8277996363A67DED0F43BD95D11C320BAB72B5A4 ) => ( 0x0000000000000000000000000000000000000000 )
  • ERC1820Registry.getInterfaceImplementer( _addr=0xfF20817765cB7f73d4bde2e66e067E58D11095C2, _interfaceHash=2C8F2151D082D980BC6C06D28B364B16371B6C228F9552BB0254F1658050629D ) => ( 0x455a3B78Cfe4B88268DBee2119eB06fB1d3F1f61 )
  • CollateralPoolPartitionValidator.tokensToPartitionToValidate( System.Byte[], _toPartition=CCCCCCCC2862B8CB21CAEDB8706D7F8B3445D8DFC790C524E3990EF014E7C578, 0xd37C580Ec0Ee0f937590E24165F31CEe0ad5021d, 0xd37C580Ec0Ee0f937590E24165F31CEe0ad5021d, _to=0x706D7F8B3445D8Dfc790C524E3990ef014e7C578, 16780000000000000000000, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCCCCCCCC2862B8CB21CAEDB8706D7F8B3445D8DFC790C524E3990EF014E7C578, 0x )
    • Amp.isCollateralManager( _collateralManager=0x706D7F8B3445D8Dfc790C524E3990ef014e7C578 ) => ( True )
    • ERC1820Registry.getInterfaceImplementer( _addr=0x706D7F8B3445D8Dfc790C524E3990ef014e7C578, _interfaceHash=FA352D6368BBC643BCF9D528FFABA5DD3E826137BC42F935045C6C227BD4C72A ) => ( 0x706D7F8B3445D8Dfc790C524E3990ef014e7C578 )
    • FlexaCollateralManager.tokensReceived( System.Byte[], _partition=CCCCCCCC2862B8CB21CAEDB8706D7F8B3445D8DFC790C524E3990EF014E7C578, _operator=0xd37C580Ec0Ee0f937590E24165F31CEe0ad5021d, 0xd37C580Ec0Ee0f937590E24165F31CEe0ad5021d, _to=0x706D7F8B3445D8Dfc790C524E3990ef014e7C578, _value=16780000000000000000000, _data=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCCCCCCCC2862B8CB21CAEDB8706D7F8B3445D8DFC790C524E3990EF014E7C578, 0x )
      transferByPartition[Amp (ln:1266)]
      File 1 of 4: Amp
      // SPDX-License-Identifier: MIT
      
      pragma solidity 0.6.10;
      
      /**
       * @dev Wrappers over Solidity's arithmetic operations with added overflow
       * checks.
       *
       * Arithmetic operations in Solidity wrap on overflow. This can easily result
       * in bugs, because programmers usually assume that an overflow raises an
       * error, which is the standard behavior in high level programming languages.
       * `SafeMath` restores this intuition by reverting the transaction when an
       * operation overflows.
       *
       * Using this library instead of the unchecked operations eliminates an entire
       * class of bugs, so it's recommended to use it always.
       */
      library SafeMath {
          /**
           * @dev Returns the addition of two unsigned integers, reverting on
           * overflow.
           *
           * Counterpart to Solidity's `+` operator.
           *
           * Requirements:
           * - Addition cannot overflow.
           */
          function add(uint256 a, uint256 b) internal pure returns (uint256) {
              uint256 c = a + b;
              require(c >= a, "SafeMath: addition overflow");
      
              return c;
          }
      
          /**
           * @dev Returns the subtraction of two unsigned integers, reverting on
           * overflow (when the result is negative).
           *
           * Counterpart to Solidity's `-` operator.
           *
           * Requirements:
           * - Subtraction cannot overflow.
           */
          function sub(uint256 a, uint256 b) internal pure returns (uint256) {
              return sub(a, b, "SafeMath: subtraction overflow");
          }
      
          /**
           * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
           * overflow (when the result is negative).
           *
           * Counterpart to Solidity's `-` operator.
           *
           * Requirements:
           * - Subtraction cannot overflow.
           */
          function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
              require(b <= a, errorMessage);
              uint256 c = a - b;
      
              return c;
          }
      
          /**
           * @dev Returns the multiplication of two unsigned integers, reverting on
           * overflow.
           *
           * Counterpart to Solidity's `*` operator.
           *
           * Requirements:
           * - Multiplication cannot overflow.
           */
          function mul(uint256 a, uint256 b) internal pure returns (uint256) {
              // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
              // benefit is lost if 'b' is also tested.
              // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
              if (a == 0) {
                  return 0;
              }
      
              uint256 c = a * b;
              require(c / a == b, "SafeMath: multiplication overflow");
      
              return c;
          }
      
          /**
           * @dev Returns the integer division of two unsigned integers. Reverts on
           * division by zero. The result is rounded towards zero.
           *
           * Counterpart to Solidity's `/` operator. Note: this function uses a
           * `revert` opcode (which leaves remaining gas untouched) while Solidity
           * uses an invalid opcode to revert (consuming all remaining gas).
           *
           * Requirements:
           * - The divisor cannot be zero.
           */
          function div(uint256 a, uint256 b) internal pure returns (uint256) {
              return div(a, b, "SafeMath: division by zero");
          }
      
          /**
           * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
           * division by zero. The result is rounded towards zero.
           *
           * Counterpart to Solidity's `/` operator. Note: this function uses a
           * `revert` opcode (which leaves remaining gas untouched) while Solidity
           * uses an invalid opcode to revert (consuming all remaining gas).
           *
           * Requirements:
           * - The divisor cannot be zero.
           */
          function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
              // Solidity only automatically asserts when dividing by 0
              require(b > 0, errorMessage);
              uint256 c = a / b;
              // assert(a == b * c + a % b); // There is no case in which this doesn't hold
      
              return c;
          }
      
          /**
           * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
           * Reverts when dividing by zero.
           *
           * Counterpart to Solidity's `%` operator. This function uses a `revert`
           * opcode (which leaves remaining gas untouched) while Solidity uses an
           * invalid opcode to revert (consuming all remaining gas).
           *
           * Requirements:
           * - The divisor cannot be zero.
           */
          function mod(uint256 a, uint256 b) internal pure returns (uint256) {
              return mod(a, b, "SafeMath: modulo by zero");
          }
      
          /**
           * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
           * Reverts with custom message when dividing by zero.
           *
           * Counterpart to Solidity's `%` operator. This function uses a `revert`
           * opcode (which leaves remaining gas untouched) while Solidity uses an
           * invalid opcode to revert (consuming all remaining gas).
           *
           * Requirements:
           * - The divisor cannot be zero.
           */
          function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
              require(b != 0, errorMessage);
              return a % b;
          }
      }
      
      /**
       * @dev Interface of the ERC20 standard as defined in the EIP.
       */
      interface IERC20 {
          /**
           * @dev Returns the amount of tokens in existence.
           */
          function totalSupply() external view returns (uint256);
      
          /**
           * @dev Returns the amount of tokens owned by `account`.
           */
          function balanceOf(address account) external view returns (uint256);
      
          /**
           * @dev Moves `amount` tokens from the caller's account to `recipient`.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * Emits a {Transfer} event.
           */
          function transfer(address recipient, uint256 amount) external returns (bool);
      
          /**
           * @dev Returns the remaining number of tokens that `spender` will be
           * allowed to spend on behalf of `owner` through {transferFrom}. This is
           * zero by default.
           *
           * This value changes when {approve} or {transferFrom} are called.
           */
          function allowance(address owner, address spender) external view returns (uint256);
      
          /**
           * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * IMPORTANT: Beware that changing an allowance with this method brings the risk
           * that someone may use both the old and the new allowance by unfortunate
           * transaction ordering. One possible solution to mitigate this race
           * condition is to first reduce the spender's allowance to 0 and set the
           * desired value afterwards:
           * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
           *
           * Emits an {Approval} event.
           */
          function approve(address spender, uint256 amount) external returns (bool);
      
          /**
           * @dev Moves `amount` tokens from `sender` to `recipient` using the
           * allowance mechanism. `amount` is then deducted from the caller's
           * allowance.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * Emits a {Transfer} event.
           */
          function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
      
          /**
           * @dev Emitted when `value` tokens are moved from one account (`from`) to
           * another (`to`).
           *
           * Note that `value` may be zero.
           */
          event Transfer(address indexed from, address indexed to, uint256 value);
      
          /**
           * @dev Emitted when the allowance of a `spender` for an `owner` is set by
           * a call to {approve}. `value` is the new allowance.
           */
          event Approval(address indexed owner, address indexed spender, uint256 value);
      }
      
      /**
       * @title Ownable is a contract the provides contract ownership functionality, including a two-
       * phase transfer.
       */
      contract Ownable {
          address private _owner;
          address private _authorizedNewOwner;
      
          /**
           * @notice Emitted when the owner authorizes ownership transfer to a new address
           * @param authorizedAddress New owner address
           */
          event OwnershipTransferAuthorization(address indexed authorizedAddress);
      
          /**
           * @notice Emitted when the authorized address assumed ownership
           * @param oldValue Old owner
           * @param newValue New owner
           */
          event OwnerUpdate(address indexed oldValue, address indexed newValue);
      
          /**
           * @notice Sets the owner to the sender / contract creator
           */
          constructor() internal {
              _owner = msg.sender;
          }
      
          /**
           * @notice Retrieves the owner of the contract
           * @return The contract owner
           */
          function owner() public view returns (address) {
              return _owner;
          }
      
          /**
           * @notice Retrieves the authorized new owner of the contract
           * @return The authorized new contract owner
           */
          function authorizedNewOwner() public view returns (address) {
              return _authorizedNewOwner;
          }
      
          /**
           * @notice Authorizes the transfer of ownership from owner to the provided address.
           * NOTE: No transfer will occur unless authorizedAddress calls assumeOwnership().
           * This authorization may be removed by another call to this function authorizing the zero
           * address.
           * @param _authorizedAddress The address authorized to become the new owner
           */
          function authorizeOwnershipTransfer(address _authorizedAddress) external {
              require(msg.sender == _owner, "Invalid sender");
      
              _authorizedNewOwner = _authorizedAddress;
      
              emit OwnershipTransferAuthorization(_authorizedNewOwner);
          }
      
          /**
           * @notice Transfers ownership of this contract to the _authorizedNewOwner
           * @dev Error invalid sender.
           */
          function assumeOwnership() external {
              require(msg.sender == _authorizedNewOwner, "Invalid sender");
      
              address oldValue = _owner;
              _owner = _authorizedNewOwner;
              _authorizedNewOwner = address(0);
      
              emit OwnerUpdate(oldValue, _owner);
          }
      }
      
      abstract contract ERC1820Registry {
          function setInterfaceImplementer(
              address _addr,
              bytes32 _interfaceHash,
              address _implementer
          ) external virtual;
      
          function getInterfaceImplementer(address _addr, bytes32 _interfaceHash)
              external
              virtual
              view
              returns (address);
      
          function setManager(address _addr, address _newManager) external virtual;
      
          function getManager(address _addr) public virtual view returns (address);
      }
      
      /// Base client to interact with the registry.
      contract ERC1820Client {
          ERC1820Registry constant ERC1820REGISTRY = ERC1820Registry(
              0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
          );
      
          function setInterfaceImplementation(
              string memory _interfaceLabel,
              address _implementation
          ) internal {
              bytes32 interfaceHash = keccak256(abi.encodePacked(_interfaceLabel));
              ERC1820REGISTRY.setInterfaceImplementer(
                  address(this),
                  interfaceHash,
                  _implementation
              );
          }
      
          function interfaceAddr(address addr, string memory _interfaceLabel)
              internal
              view
              returns (address)
          {
              bytes32 interfaceHash = keccak256(abi.encodePacked(_interfaceLabel));
              return ERC1820REGISTRY.getInterfaceImplementer(addr, interfaceHash);
          }
      
          function delegateManagement(address _newManager) internal {
              ERC1820REGISTRY.setManager(address(this), _newManager);
          }
      }
      
      contract ERC1820Implementer {
          /**
           * @dev ERC1820 well defined magic value indicating the contract has
           * registered with the ERC1820Registry that it can implement an interface.
           */
          bytes32 constant ERC1820_ACCEPT_MAGIC = keccak256(
              abi.encodePacked("ERC1820_ACCEPT_MAGIC")
          );
      
          /**
           * @dev Mapping of interface name keccak256 hashes for which this contract
           * implements the interface.
           * @dev Only settable internally.
           */
          mapping(bytes32 => bool) internal _interfaceHashes;
      
          /**
           * @notice Indicates whether the contract implements the interface `_interfaceHash`
           * for the address `_addr`.
           * @param _interfaceHash keccak256 hash of the name of the interface.
           * @return ERC1820_ACCEPT_MAGIC only if the contract implements `ìnterfaceHash`
           * for the address `_addr`.
           * @dev In this implementation, the `_addr` (the address for which the
           * contract will implement the interface) is always `address(this)`.
           */
          function canImplementInterfaceForAddress(
              bytes32 _interfaceHash,
              address // Comments to avoid compilation warnings for unused variables. /*addr*/
          ) external view returns (bytes32) {
              if (_interfaceHashes[_interfaceHash]) {
                  return ERC1820_ACCEPT_MAGIC;
              } else {
                  return "";
              }
          }
      
          /**
           * @notice Internally set the fact this contract implements the interface
           * identified by `_interfaceLabel`
           * @param _interfaceLabel String representation of the interface.
           */
          function _setInterface(string memory _interfaceLabel) internal {
              _interfaceHashes[keccak256(abi.encodePacked(_interfaceLabel))] = true;
          }
      }
      
      /**
       * @title IAmpTokensSender
       * @dev IAmpTokensSender token transfer hook interface
       */
      interface IAmpTokensSender {
          /**
           * @dev Report if the transfer will succeed from the pespective of the
           * token sender
           */
          function canTransfer(
              bytes4 functionSig,
              bytes32 partition,
              address operator,
              address from,
              address to,
              uint256 value,
              bytes calldata data,
              bytes calldata operatorData
          ) external view returns (bool);
      
          /**
           * @dev Hook executed upon a transfer on behalf of the sender
           */
          function tokensToTransfer(
              bytes4 functionSig,
              bytes32 partition,
              address operator,
              address from,
              address to,
              uint256 value,
              bytes calldata data,
              bytes calldata operatorData
          ) external;
      }
      
      /**
       * @title IAmpTokensRecipient
       * @dev IAmpTokensRecipient token transfer hook interface
       */
      interface IAmpTokensRecipient {
          /**
           * @dev Report if the recipient will successfully receive the tokens
           */
          function canReceive(
              bytes4 functionSig,
              bytes32 partition,
              address operator,
              address from,
              address to,
              uint256 value,
              bytes calldata data,
              bytes calldata operatorData
          ) external view returns (bool);
      
          /**
           * @dev Hook executed upon a transfer to the recipient
           */
          function tokensReceived(
              bytes4 functionSig,
              bytes32 partition,
              address operator,
              address from,
              address to,
              uint256 value,
              bytes calldata data,
              bytes calldata operatorData
          ) external;
      }
      
      /**
       * @notice Partition strategy validator hooks for Amp
       */
      interface IAmpPartitionStrategyValidator {
          function tokensFromPartitionToValidate(
              bytes4 _functionSig,
              bytes32 _partition,
              address _operator,
              address _from,
              address _to,
              uint256 _value,
              bytes calldata _data,
              bytes calldata _operatorData
          ) external;
      
          function tokensToPartitionToValidate(
              bytes4 _functionSig,
              bytes32 _partition,
              address _operator,
              address _from,
              address _to,
              uint256 _value,
              bytes calldata _data,
              bytes calldata _operatorData
          ) external;
      
          function isOperatorForPartitionScope(
              bytes32 _partition,
              address _operator,
              address _tokenHolder
          ) external view returns (bool);
      }
      
      /**
       * @title PartitionUtils
       * @notice Partition related helper functions.
       */
      
      library PartitionUtils {
          bytes32 public constant CHANGE_PARTITION_FLAG = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
      
          /**
           * @notice Retrieve the destination partition from the 'data' field.
           * A partition change is requested ONLY when 'data' starts with the flag:
           *
           *   0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
           *
           * When the flag is detected, the destination partition is extracted from the
           * 32 bytes following the flag.
           * @param _data Information attached to the transfer. Will contain the
           * destination partition if a change is requested.
           * @param _fallbackPartition Partition value to return if a partition change
           * is not requested in the `_data`.
           * @return toPartition Destination partition. If the `_data` does not contain
           * the prefix and bytes32 partition in the first 64 bytes, the method will
           * return the provided `_fromPartition`.
           */
          function _getDestinationPartition(bytes memory _data, bytes32 _fallbackPartition)
              internal
              pure
              returns (bytes32)
          {
              if (_data.length < 64) {
                  return _fallbackPartition;
              }
      
              (bytes32 flag, bytes32 toPartition) = abi.decode(_data, (bytes32, bytes32));
              if (flag == CHANGE_PARTITION_FLAG) {
                  return toPartition;
              }
      
              return _fallbackPartition;
          }
      
          /**
           * @notice Helper to get the strategy identifying prefix from the `_partition`.
           * @param _partition Partition to get the prefix for.
           * @return 4 byte partition strategy prefix.
           */
          function _getPartitionPrefix(bytes32 _partition) internal pure returns (bytes4) {
              return bytes4(_partition);
          }
      
          /**
           * @notice Helper method to split the partition into the prefix, sub partition
           * and partition owner components.
           * @param _partition The partition to split into parts.
           * @return The 4 byte partition prefix, 8 byte sub partition, and final 20
           * bytes representing an address.
           */
          function _splitPartition(bytes32 _partition)
              internal
              pure
              returns (
                  bytes4,
                  bytes8,
                  address
              )
          {
              bytes4 prefix = bytes4(_partition);
              bytes8 subPartition = bytes8(_partition << 32);
              address addressPart = address(uint160(uint256(_partition)));
              return (prefix, subPartition, addressPart);
          }
      
          /**
           * @notice Helper method to get a partition strategy ERC1820 interface name
           * based on partition prefix.
           * @param _prefix 4 byte partition prefix.
           * @dev Each 4 byte prefix has a unique interface name so that an individual
           * hook implementation can be set for each prefix.
           */
          function _getPartitionStrategyValidatorIName(bytes4 _prefix)
              internal
              pure
              returns (string memory)
          {
              return string(abi.encodePacked("AmpPartitionStrategyValidator", _prefix));
          }
      }
      
      /**
       * @title ErrorCodes
       * @notice Amp error codes.
       */
      contract ErrorCodes {
          string internal EC_50_TRANSFER_FAILURE = "50";
          string internal EC_51_TRANSFER_SUCCESS = "51";
          string internal EC_52_INSUFFICIENT_BALANCE = "52";
          string internal EC_53_INSUFFICIENT_ALLOWANCE = "53";
      
          string internal EC_56_INVALID_SENDER = "56";
          string internal EC_57_INVALID_RECEIVER = "57";
          string internal EC_58_INVALID_OPERATOR = "58";
      
          string internal EC_59_INSUFFICIENT_RIGHTS = "59";
      
          string internal EC_5A_INVALID_SWAP_TOKEN_ADDRESS = "5A";
          string internal EC_5B_INVALID_VALUE_0 = "5B";
          string internal EC_5C_ADDRESS_CONFLICT = "5C";
          string internal EC_5D_PARTITION_RESERVED = "5D";
          string internal EC_5E_PARTITION_PREFIX_CONFLICT = "5E";
          string internal EC_5F_INVALID_PARTITION_PREFIX_0 = "5F";
          string internal EC_60_SWAP_TRANSFER_FAILURE = "60";
      }
      
      interface ISwapToken {
          function allowance(address owner, address spender)
              external
              view
              returns (uint256 remaining);
      
          function transferFrom(
              address from,
              address to,
              uint256 value
          ) external returns (bool success);
      }
      
      /**
       * @title Amp
       * @notice Amp is an ERC20 compatible collateral token designed to support
       * multiple classes of collateralization systems.
       * @dev The Amp token contract includes the following features:
       *
       * Partitions
       *   Tokens can be segmented within a given address by "partition", which in
       *   pracice is a 32 byte identifier. These partitions can have unique
       *   permissions globally, through the using of partition strategies, and
       *   locally, on a per address basis. The ability to create the sub-segments
       *   of tokens and assign special behavior gives collateral managers
       *   flexibility in how they are implemented.
       *
       * Operators
       *   Inspired by ERC777, Amp allows token holders to assign "operators" on
       *   all (or any number of partitions) of their tokens. Operators are allowed
       *   to execute transfers on behalf of token owners without the need to use the
       *   ERC20 "allowance" semantics.
       *
       * Transfers with Data
       *   Inspired by ERC777, Amp transfers can include arbitrary data, as well as
       *   operator data. This data can be used to change the partition of tokens,
       *   be used by collateral manager hooks to validate a transfer, be propagated
       *   via event to an off chain system, etc.
       *
       * Token Transfer Hooks on Send and Receive
       *   Inspired by ERC777, Amp uses the ERC1820 Registry to allow collateral
       *   manager implementations to register hooks to be called upon sending to
       *   or transferring from the collateral manager's address or, using partition
       *   strategies, owned partition space. The hook implementations can be used
       *   to validate transfer properties, gate transfers, emit custom events,
       *   update local state, etc.
       *
       * Collateral Management Partition Strategies
       *   Amp is able to define certain sets of partitions, identified by a 4 byte
       *   prefix, that will allow special, custom logic to be executed when transfers
       *   are made to or from those partitions. This opens up the possibility of
       *   entire classes of collateral management systems that would not be possible
       *   without it.
       *
       * These features give collateral manager implementers flexibility while
       * providing a consistent, "collateral-in-place", interface for interacting
       * with collateral systems directly through the Amp contract.
       */
      contract Amp is IERC20, ERC1820Client, ERC1820Implementer, ErrorCodes, Ownable {
          using SafeMath for uint256;
      
          /**************************************************************************/
          /********************** ERC1820 Interface Constants ***********************/
      
          /**
           * @dev AmpToken interface label.
           */
          string internal constant AMP_INTERFACE_NAME = "AmpToken";
      
          /**
           * @dev ERC20Token interface label.
           */
          string internal constant ERC20_INTERFACE_NAME = "ERC20Token";
      
          /**
           * @dev AmpTokensSender interface label.
           */
          string internal constant AMP_TOKENS_SENDER = "AmpTokensSender";
      
          /**
           * @dev AmpTokensRecipient interface label.
           */
          string internal constant AMP_TOKENS_RECIPIENT = "AmpTokensRecipient";
      
          /**
           * @dev AmpTokensChecker interface label.
           */
          string internal constant AMP_TOKENS_CHECKER = "AmpTokensChecker";
      
          /**************************************************************************/
          /*************************** Token properties *****************************/
      
          /**
           * @dev Token name (Amp).
           */
          string internal _name;
      
          /**
           * @dev Token symbol (AMP).
           */
          string internal _symbol;
      
          /**
           * @dev Total minted supply of token. This will increase comensurately with
           * successful swaps of the swap token.
           */
          uint256 internal _totalSupply;
      
          /**
           * @dev The granularity of the token. Hard coded to 1.
           */
          uint256 internal constant _granularity = 1;
      
          /**************************************************************************/
          /***************************** Token mappings *****************************/
      
          /**
           * @dev Mapping from tokenHolder to balance. This reflects the balance
           * across all partitions of an address.
           */
          mapping(address => uint256) internal _balances;
      
          /**************************************************************************/
          /************************** Partition mappings ****************************/
      
          /**
           * @dev List of active partitions. This list reflects all partitions that
           * have tokens assigned to them.
           */
          bytes32[] internal _totalPartitions;
      
          /**
           * @dev Mapping from partition to their index.
           */
          mapping(bytes32 => uint256) internal _indexOfTotalPartitions;
      
          /**
           * @dev Mapping from partition to global balance of corresponding partition.
           */
          mapping(bytes32 => uint256) public totalSupplyByPartition;
      
          /**
           * @dev Mapping from tokenHolder to their partitions.
           */
          mapping(address => bytes32[]) internal _partitionsOf;
      
          /**
           * @dev Mapping from (tokenHolder, partition) to their index.
           */
          mapping(address => mapping(bytes32 => uint256)) internal _indexOfPartitionsOf;
      
          /**
           * @dev Mapping from (tokenHolder, partition) to balance of corresponding
           * partition.
           */
          mapping(address => mapping(bytes32 => uint256)) internal _balanceOfByPartition;
      
          /**
           * @notice Default partition of the token.
           * @dev All ERC20 operations operate solely on this partition.
           */
          bytes32
              public constant defaultPartition = 0x0000000000000000000000000000000000000000000000000000000000000000;
      
          /**
           * @dev Zero partition prefix. Parititions with this prefix can not have
           * a strategy assigned, and partitions with a different prefix must have one.
           */
          bytes4 internal constant ZERO_PREFIX = 0x00000000;
      
          /**************************************************************************/
          /***************************** Operator mappings **************************/
      
          /**
           * @dev Mapping from (tokenHolder, operator) to authorized status. This is
           * specific to the token holder.
           */
          mapping(address => mapping(address => bool)) internal _authorizedOperator;
      
          /**************************************************************************/
          /********************** Partition operator mappings ***********************/
      
          /**
           * @dev Mapping from (partition, tokenHolder, spender) to allowed value.
           * This is specific to the token holder.
           */
          mapping(bytes32 => mapping(address => mapping(address => uint256)))
              internal _allowedByPartition;
      
          /**
           * @dev Mapping from (tokenHolder, partition, operator) to 'approved for
           * partition' status. This is specific to the token holder.
           */
          mapping(address => mapping(bytes32 => mapping(address => bool)))
              internal _authorizedOperatorByPartition;
      
          /**************************************************************************/
          /********************** Collateral Manager mappings ***********************/
          /**
           * @notice Collection of registered collateral managers.
           */
          address[] public collateralManagers;
          /**
           * @dev Mapping of collateral manager addresses to registration status.
           */
          mapping(address => bool) internal _isCollateralManager;
      
          /**************************************************************************/
          /********************* Partition Strategy mappings ************************/
      
          /**
           * @notice Collection of reserved partition strategies.
           */
          bytes4[] public partitionStrategies;
      
          /**
           * @dev Mapping of partition strategy flag to registration status.
           */
          mapping(bytes4 => bool) internal _isPartitionStrategy;
      
          /**************************************************************************/
          /***************************** Swap storage *******************************/
      
          /**
           * @notice Swap token address. Immutable.
           */
          ISwapToken public swapToken;
      
          /**
           * @notice Swap token graveyard address.
           * @dev This is the address that the incoming swapped tokens will be
           * forwarded to upon successfully minting Amp.
           */
          address
              public constant swapTokenGraveyard = 0x000000000000000000000000000000000000dEaD;
      
          /**************************************************************************/
          /** EVENTS ****************************************************************/
          /**************************************************************************/
      
          /**************************************************************************/
          /**************************** Transfer Events *****************************/
      
          /**
           * @notice Emitted when a transfer has been successfully completed.
           * @param fromPartition The partition the tokens were transfered from.
           * @param operator The address that initiated the transfer.
           * @param from The address the tokens were transferred from.
           * @param to The address the tokens were transferred to.
           * @param value The amount of tokens transferred.
           * @param data Additional metadata included with the transfer. Can include
           * the partition the tokens were transferred to (if different than
           * `fromPartition`).
           * @param operatorData Additional metadata included with the transfer on
           * behalf of the operator.
           */
          event TransferByPartition(
              bytes32 indexed fromPartition,
              address operator,
              address indexed from,
              address indexed to,
              uint256 value,
              bytes data,
              bytes operatorData
          );
      
          /**
           * @notice Emitted when a transfer has been successfully completed and the
           * tokens that were transferred have changed partitions.
           * @param fromPartition The partition the tokens were transfered from.
           * @param toPartition The partition the tokens were transfered to.
           * @param value The amount of tokens transferred.
           */
          event ChangedPartition(
              bytes32 indexed fromPartition,
              bytes32 indexed toPartition,
              uint256 value
          );
      
          /**************************************************************************/
          /**************************** Operator Events *****************************/
      
          /**
           * @notice Emitted when a token holder specifies an amount of tokens in a
           * a partition that an operator can transfer.
           * @param partition The partition of the tokens the holder has authorized the
           * operator to transfer from.
           * @param owner The token holder.
           * @param spender The operator the `owner` has authorized the allowance for.
           */
          event ApprovalByPartition(
              bytes32 indexed partition,
              address indexed owner,
              address indexed spender,
              uint256 value
          );
      
          /**
           * @notice Emitted when a token holder has authorized an operator for their
           * tokens.
           * @dev This event applies to the token holder address across all partitions.
           * @param operator The address that was authorized to transfer tokens on
           * behalf of the `tokenHolder`.
           * @param tokenHolder The address that authorized the `operator` to transfer
           * their tokens.
           */
          event AuthorizedOperator(address indexed operator, address indexed tokenHolder);
      
          /**
           * @notice Emitted when a token holder has de-authorized an operator from
           * transferring their tokens.
           * @dev This event applies to the token holder address across all partitions.
           * @param operator The address that was de-authorized from transferring tokens
           * on behalf of the `tokenHolder`.
           * @param tokenHolder The address that revoked the `operator`'s permission
           * to transfer their tokens.
           */
          event RevokedOperator(address indexed operator, address indexed tokenHolder);
      
          /**
           * @notice Emitted when a token holder has authorized an operator to transfer
           * their tokens of one partition.
           * @param partition The partition the `operator` is allowed to transfer
           * tokens from.
           * @param operator The address that was authorized to transfer tokens on
           * behalf of the `tokenHolder`.
           * @param tokenHolder The address that authorized the `operator` to transfer
           * their tokens in `partition`.
           */
          event AuthorizedOperatorByPartition(
              bytes32 indexed partition,
              address indexed operator,
              address indexed tokenHolder
          );
      
          /**
           * @notice Emitted when a token holder has de-authorized an operator from
           * transferring their tokens from a specific partition.
           * @param partition The partition the `operator` is no longer allowed to
           * transfer tokens from on behalf of the `tokenHolder`.
           * @param operator The address that was de-authorized from transferring
           * tokens on behalf of the `tokenHolder`.
           * @param tokenHolder The address that revoked the `operator`'s permission
           * to transfer their tokens from `partition`.
           */
          event RevokedOperatorByPartition(
              bytes32 indexed partition,
              address indexed operator,
              address indexed tokenHolder
          );
      
          /**************************************************************************/
          /********************** Collateral Manager Events *************************/
      
          /**
           * @notice Emitted when a collateral manager has been registered.
           * @param collateralManager The address of the collateral manager.
           */
          event CollateralManagerRegistered(address collateralManager);
      
          /**************************************************************************/
          /*********************** Partition Strategy Events ************************/
      
          /**
           * @notice Emitted when a new partition strategy validator is set.
           * @param flag The 4 byte prefix of the partitions that the stratgy affects.
           * @param name The name of the partition strategy.
           * @param implementation The address of the partition strategy hook
           * implementation.
           */
          event PartitionStrategySet(bytes4 flag, string name, address indexed implementation);
      
          // ************** Mint & Swap **************
      
          /**
           * @notice Emitted when tokens are minted as a result of a token swap
           * @param operator Address that executed the swap that resulted in tokens being minted
           * @param to Address that received the newly minted tokens.
           * @param value Amount of tokens minted
           * @param data Empty bytes, required for interface compatibility
           */
          event Minted(address indexed operator, address indexed to, uint256 value, bytes data);
      
          /**
           * @notice Indicates tokens swapped for Amp.
           * @dev The tokens that are swapped for Amp will be transferred to a
           * graveyard address that is for all practical purposes inaccessible.
           * @param operator Address that executed the swap.
           * @param from Address that the tokens were swapped from, and Amp minted for.
           * @param value Amount of tokens swapped into Amp.
           */
          event Swap(address indexed operator, address indexed from, uint256 value);
      
          /**************************************************************************/
          /** CONSTRUCTOR ***********************************************************/
          /**************************************************************************/
      
          /**
           * @notice Initialize Amp, initialize the default partition, and register the
           * contract implementation in the global ERC1820Registry.
           * @param _swapTokenAddress_ The address of the ERC20 token that is set to be
           * swappable for Amp.
           * @param _name_ Name of the token.
           * @param _symbol_ Symbol of the token.
           */
          constructor(
              address _swapTokenAddress_,
              string memory _name_,
              string memory _symbol_
          ) public {
              // "Swap token cannot be 0 address"
              require(_swapTokenAddress_ != address(0), EC_5A_INVALID_SWAP_TOKEN_ADDRESS);
              swapToken = ISwapToken(_swapTokenAddress_);
      
              _name = _name_;
              _symbol = _symbol_;
              _totalSupply = 0;
      
              // Add the default partition to the total partitions on deploy
              _addPartitionToTotalPartitions(defaultPartition);
      
              // Register contract in ERC1820 registry
              ERC1820Client.setInterfaceImplementation(AMP_INTERFACE_NAME, address(this));
              ERC1820Client.setInterfaceImplementation(ERC20_INTERFACE_NAME, address(this));
      
              // Indicate token verifies Amp and ERC20 interfaces
              ERC1820Implementer._setInterface(AMP_INTERFACE_NAME);
              ERC1820Implementer._setInterface(ERC20_INTERFACE_NAME);
          }
      
          /**************************************************************************/
          /** EXTERNAL FUNCTIONS (ERC20) ********************************************/
          /**************************************************************************/
      
          /**
           * @notice Get the total number of issued tokens.
           * @return Total supply of tokens currently in circulation.
           */
          function totalSupply() external override view returns (uint256) {
              return _totalSupply;
          }
      
          /**
           * @notice Get the balance of the account with address `_tokenHolder`.
           * @dev This returns the balance of the holder across all partitions. Note
           * that due to other functionality in Amp, this figure should not be used
           * as the arbiter of the amount a token holder will successfully be able to
           * send via the ERC20 compatible `transfer` method. In order to get that
           * figure, use `balanceOfByParition` and to get the balance of the default
           * partition.
           * @param _tokenHolder Address for which the balance is returned.
           * @return Amount of token held by `_tokenHolder` in the default partition.
           */
          function balanceOf(address _tokenHolder) external override view returns (uint256) {
              return _balances[_tokenHolder];
          }
      
          /**
           * @notice Transfer token for a specified address.
           * @dev This method is for ERC20 compatibility, and only affects the
           * balance of the `msg.sender` address's default partition.
           * @param _to The address to transfer to.
           * @param _value The value to be transferred.
           * @return A boolean that indicates if the operation was successful.
           */
          function transfer(address _to, uint256 _value) external override returns (bool) {
              _transferByDefaultPartition(msg.sender, msg.sender, _to, _value, "");
              return true;
          }
      
          /**
           * @notice Transfer tokens from one address to another.
           * @dev This method is for ERC20 compatibility, and only affects the
           * balance and allowance of the `_from` address's default partition.
           * @param _from The address which you want to transfer tokens from.
           * @param _to The address which you want to transfer to.
           * @param _value The amount of tokens to be transferred.
           * @return A boolean that indicates if the operation was successful.
           */
          function transferFrom(
              address _from,
              address _to,
              uint256 _value
          ) external override returns (bool) {
              _transferByDefaultPartition(msg.sender, _from, _to, _value, "");
              return true;
          }
      
          /**
           * @notice Check the value of tokens that an owner allowed to a spender.
           * @dev This method is for ERC20 compatibility, and only affects the
           * allowance of the `msg.sender`'s default partition.
           * @param _owner address The address which owns the funds.
           * @param _spender address The address which will spend the funds.
           * @return A uint256 specifying the value of tokens still available for the
           * spender.
           */
          function allowance(address _owner, address _spender)
              external
              override
              view
              returns (uint256)
          {
              return _allowedByPartition[defaultPartition][_owner][_spender];
          }
      
          /**
           * @notice Approve the passed address to spend the specified amount of
           * tokens from the default partition on behalf of 'msg.sender'.
           * @dev This method is for ERC20 compatibility, and only affects the
           * allowance of the `msg.sender`'s default partition.
           * @param _spender The address which will spend the funds.
           * @param _value The amount of tokens to be spent.
           * @return A boolean that indicates if the operation was successful.
           */
          function approve(address _spender, uint256 _value) external override returns (bool) {
              _approveByPartition(defaultPartition, msg.sender, _spender, _value);
              return true;
          }
      
          /**
           * @notice Atomically increases the allowance granted to `_spender` by the
           * for caller.
           * @dev This is an alternative to {approve} that can be used as a mitigation
           * problems described in {IERC20-approve}.
           * Emits an {Approval} event indicating the updated allowance.
           * Requirements:
           * - `_spender` cannot be the zero address.
           * @dev This method is for ERC20 compatibility, and only affects the
           * allowance of the `msg.sender`'s default partition.
           * @param _spender Operator allowed to transfer the tokens
           * @param _addedValue Additional amount of the `msg.sender`s tokens `_spender`
           * is allowed to transfer
           * @return 'true' is successful, 'false' otherwise
           */
          function increaseAllowance(address _spender, uint256 _addedValue)
              external
              returns (bool)
          {
              _approveByPartition(
                  defaultPartition,
                  msg.sender,
                  _spender,
                  _allowedByPartition[defaultPartition][msg.sender][_spender].add(_addedValue)
              );
              return true;
          }
      
          /**
           * @notice Atomically decreases the allowance granted to `_spender` by the
           * caller.
           * @dev This is an alternative to {approve} that can be used as a mitigation
           * for bugs caused by reentrancy.
           * Emits an {Approval} event indicating the updated allowance.
           * Requirements:
           * - `_spender` cannot be the zero address.
           * - `_spender` must have allowance for the caller of at least
           * `_subtractedValue`.
           * @dev This method is for ERC20 compatibility, and only affects the
           * allowance of the `msg.sender`'s default partition.
           * @param _spender Operator allowed to transfer the tokens
           * @param _subtractedValue Amount of the `msg.sender`s tokens `_spender`
           * is no longer allowed to transfer
           * @return 'true' is successful, 'false' otherwise
           */
          function decreaseAllowance(address _spender, uint256 _subtractedValue)
              external
              returns (bool)
          {
              _approveByPartition(
                  defaultPartition,
                  msg.sender,
                  _spender,
                  _allowedByPartition[defaultPartition][msg.sender][_spender].sub(
                      _subtractedValue
                  )
              );
              return true;
          }
      
          /**************************************************************************/
          /** EXTERNAL FUNCTIONS (AMP) **********************************************/
          /**************************************************************************/
      
          /******************************** Swap  ***********************************/
      
          /**
           * @notice Swap tokens to mint AMP.
           * @dev Requires `_from` to have given allowance of swap token to contract.
           * Otherwise will throw error code 53 (Insuffient Allowance).
           * @param _from Token holder to execute the swap for.
           */
          function swap(address _from) public {
              uint256 amount = swapToken.allowance(_from, address(this));
              require(amount > 0, EC_53_INSUFFICIENT_ALLOWANCE);
      
              require(
                  swapToken.transferFrom(_from, swapTokenGraveyard, amount),
                  EC_60_SWAP_TRANSFER_FAILURE
              );
      
              _mint(msg.sender, _from, amount);
      
              emit Swap(msg.sender, _from, amount);
          }
      
          /**************************************************************************/
          /************************** Holder information ****************************/
      
          /**
           * @notice Get balance of a tokenholder for a specific partition.
           * @param _partition Name of the partition.
           * @param _tokenHolder Address for which the balance is returned.
           * @return Amount of token of partition `_partition` held by `_tokenHolder` in the token contract.
           */
          function balanceOfByPartition(bytes32 _partition, address _tokenHolder)
              external
              view
              returns (uint256)
          {
              return _balanceOfByPartition[_tokenHolder][_partition];
          }
      
          /**
           * @notice Get partitions index of a token holder.
           * @param _tokenHolder Address for which the partitions index are returned.
           * @return Array of partitions index of '_tokenHolder'.
           */
          function partitionsOf(address _tokenHolder) external view returns (bytes32[] memory) {
              return _partitionsOf[_tokenHolder];
          }
      
          /**************************************************************************/
          /************************** Advanced Transfers ****************************/
      
          /**
           * @notice Transfer tokens from a specific partition on behalf of a token
           * holder, optionally changing the parittion and optionally including
           * arbitrary data with the transfer.
           * @dev This can be used to transfer an address's own tokens, or transfer
           * a different addresses tokens by specifying the `_from` param. If
           * attempting to transfer from a different address than `msg.sender`, the
           * `msg.sender` will need to be an operator or have enough allowance for the
           * `_partition` of the `_from` address.
           * @param _partition Name of the partition to transfer from.
           * @param _from Token holder.
           * @param _to Token recipient.
           * @param _value Number of tokens to transfer.
           * @param _data Information attached to the transfer. Will contain the
           * destination partition (if changing partitions).
           * @param _operatorData Information attached to the transfer, by the operator.
           * @return Destination partition.
           */
          function transferByPartition(
              bytes32 _partition,
              address _from,
              address _to,
              uint256 _value,
              bytes calldata _data,
              bytes calldata _operatorData
          ) external returns (bytes32) {
              return
                  _transferByPartition(
                      _partition,
                      msg.sender,
                      _from,
                      _to,
                      _value,
                      _data,
                      _operatorData
                  );
          }
      
          /**************************************************************************/
          /************************** Operator Management ***************************/
      
          /**
           * @notice Set a third party operator address as an operator of 'msg.sender'
           * to transfer and redeem tokens on its behalf.
           * @dev The msg.sender is always an operator for itself, and does not need to
           * be explicitly added.
           * @param _operator Address to set as an operator for 'msg.sender'.
           */
          function authorizeOperator(address _operator) external {
              require(_operator != msg.sender, EC_58_INVALID_OPERATOR);
      
              _authorizedOperator[msg.sender][_operator] = true;
              emit AuthorizedOperator(_operator, msg.sender);
          }
      
          /**
           * @notice Remove the right of the operator address to be an operator for
           * 'msg.sender' and to transfer and redeem tokens on its behalf.
           * @dev The msg.sender is always an operator for itself, and cannot be
           * removed.
           * @param _operator Address to rescind as an operator for 'msg.sender'.
           */
          function revokeOperator(address _operator) external {
              require(_operator != msg.sender, EC_58_INVALID_OPERATOR);
      
              _authorizedOperator[msg.sender][_operator] = false;
              emit RevokedOperator(_operator, msg.sender);
          }
      
          /**
           * @notice Set `_operator` as an operator for 'msg.sender' for a given partition.
           * @dev The msg.sender is always an operator for itself, and does not need to
           * be explicitly added to a partition.
           * @param _partition Name of the partition.
           * @param _operator Address to set as an operator for 'msg.sender'.
           */
          function authorizeOperatorByPartition(bytes32 _partition, address _operator)
              external
          {
              require(_operator != msg.sender, EC_58_INVALID_OPERATOR);
      
              _authorizedOperatorByPartition[msg.sender][_partition][_operator] = true;
              emit AuthorizedOperatorByPartition(_partition, _operator, msg.sender);
          }
      
          /**
           * @notice Remove the right of the operator address to be an operator on a
           * given partition for 'msg.sender' and to transfer and redeem tokens on its
           * behalf.
           * @dev The msg.sender is always an operator for itself, and cannot be
           * removed from a partition.
           * @param _partition Name of the partition.
           * @param _operator Address to rescind as an operator on given partition for
           * 'msg.sender'.
           */
          function revokeOperatorByPartition(bytes32 _partition, address _operator) external {
              require(_operator != msg.sender, EC_58_INVALID_OPERATOR);
      
              _authorizedOperatorByPartition[msg.sender][_partition][_operator] = false;
              emit RevokedOperatorByPartition(_partition, _operator, msg.sender);
          }
      
          /**************************************************************************/
          /************************** Operator Information **************************/
          /**
           * @notice Indicate whether the `_operator` address is an operator of the
           * `_tokenHolder` address.
           * @dev An operator in this case is an operator across all of the partitions
           * of the `msg.sender` address.
           * @param _operator Address which may be an operator of `_tokenHolder`.
           * @param _tokenHolder Address of a token holder which may have the
           * `_operator` address as an operator.
           * @return 'true' if operator is an operator of 'tokenHolder' and 'false'
           * otherwise.
           */
          function isOperator(address _operator, address _tokenHolder)
              external
              view
              returns (bool)
          {
              return _isOperator(_operator, _tokenHolder);
          }
      
          /**
           * @notice Indicate whether the operator address is an operator of the
           * `_tokenHolder` address for the given partition.
           * @param _partition Name of the partition.
           * @param _operator Address which may be an operator of tokenHolder for the
           * given partition.
           * @param _tokenHolder Address of a token holder which may have the
           * `_operator` address as an operator for the given partition.
           * @return 'true' if 'operator' is an operator of `_tokenHolder` for
           * partition '_partition' and 'false' otherwise.
           */
          function isOperatorForPartition(
              bytes32 _partition,
              address _operator,
              address _tokenHolder
          ) external view returns (bool) {
              return _isOperatorForPartition(_partition, _operator, _tokenHolder);
          }
      
          /**
           * @notice Indicate when the `_operator` address is an operator of the
           * `_collateralManager` address for the given partition.
           * @dev This method is the same as `isOperatorForPartition`, except that it
           * also requires the address that `_operator` is being checked for MUST be
           * a registered collateral manager, and this method will not execute
           * partition strategy operator check hooks.
           * @param _partition Name of the partition.
           * @param _operator Address which may be an operator of `_collateralManager`
           * for the given partition.
           * @param _collateralManager Address of a collateral manager which may have
           * the `_operator` address as an operator for the given partition.
           */
          function isOperatorForCollateralManager(
              bytes32 _partition,
              address _operator,
              address _collateralManager
          ) external view returns (bool) {
              return
                  _isCollateralManager[_collateralManager] &&
                  (_isOperator(_operator, _collateralManager) ||
                      _authorizedOperatorByPartition[_collateralManager][_partition][_operator]);
          }
      
          /**************************************************************************/
          /***************************** Token metadata *****************************/
          /**
           * @notice Get the name of the token (Amp).
           * @return Name of the token.
           */
          function name() external view returns (string memory) {
              return _name;
          }
      
          /**
           * @notice Get the symbol of the token (AMP).
           * @return Symbol of the token.
           */
          function symbol() external view returns (string memory) {
              return _symbol;
          }
      
          /**
           * @notice Get the number of decimals of the token.
           * @dev Hard coded to 18.
           * @return The number of decimals of the token (18).
           */
          function decimals() external pure returns (uint8) {
              return uint8(18);
          }
      
          /**
           * @notice Get the smallest part of the token that’s not divisible.
           * @dev Hard coded to 1.
           * @return The smallest non-divisible part of the token.
           */
          function granularity() external pure returns (uint256) {
              return _granularity;
          }
      
          /**
           * @notice Get list of existing partitions.
           * @return Array of all exisiting partitions.
           */
          function totalPartitions() external view returns (bytes32[] memory) {
              return _totalPartitions;
          }
      
          /************************************************************************************************/
          /******************************** Partition Token Allowances ************************************/
          /**
           * @notice Check the value of tokens that an owner allowed to a spender.
           * @param _partition Name of the partition.
           * @param _owner The address which owns the tokens.
           * @param _spender The address which will spend the tokens.
           * @return The value of tokens still for the spender to transfer.
           */
          function allowanceByPartition(
              bytes32 _partition,
              address _owner,
              address _spender
          ) external view returns (uint256) {
              return _allowedByPartition[_partition][_owner][_spender];
          }
      
          /**
           * @notice Approve the `_spender` address to spend the specified amount of
           * tokens in `_partition` on behalf of 'msg.sender'.
           * @param _partition Name of the partition.
           * @param _spender The address which will spend the tokens.
           * @param _value The amount of tokens to be tokens.
           * @return A boolean that indicates if the operation was successful.
           */
          function approveByPartition(
              bytes32 _partition,
              address _spender,
              uint256 _value
          ) external returns (bool) {
              _approveByPartition(_partition, msg.sender, _spender, _value);
              return true;
          }
      
          /**
           * @notice Atomically increases the allowance granted to `_spender` by the
           * caller.
           * @dev This is an alternative to {approveByPartition} that can be used as
           * a mitigation for bugs caused by reentrancy.
           * Emits an {ApprovalByPartition} event indicating the updated allowance.
           * Requirements:
           * - `_spender` cannot be the zero address.
           * @param _partition Name of the partition.
           * @param _spender Operator allowed to transfer the tokens
           * @param _addedValue Additional amount of the `msg.sender`s tokens `_spender`
           * is allowed to transfer
           * @return 'true' is successful, 'false' otherwise
           */
          function increaseAllowanceByPartition(
              bytes32 _partition,
              address _spender,
              uint256 _addedValue
          ) external returns (bool) {
              _approveByPartition(
                  _partition,
                  msg.sender,
                  _spender,
                  _allowedByPartition[_partition][msg.sender][_spender].add(_addedValue)
              );
              return true;
          }
      
          /**
           * @notice Atomically decreases the allowance granted to `_spender` by the
           * caller.
           * @dev This is an alternative to {approveByPartition} that can be used as
           * a mitigation for bugs caused by reentrancy.
           * Emits an {ApprovalByPartition} event indicating the updated allowance.
           * Requirements:
           * - `_spender` cannot be the zero address.
           * - `_spender` must have allowance for the caller of at least
           * `_subtractedValue`.
           * @param _spender Operator allowed to transfer the tokens
           * @param _subtractedValue Amount of the `msg.sender`s tokens `_spender` is
           * no longer allowed to transfer
           * @return 'true' is successful, 'false' otherwise
           */
          function decreaseAllowanceByPartition(
              bytes32 _partition,
              address _spender,
              uint256 _subtractedValue
          ) external returns (bool) {
              // TOOD: Figure out if safe math will panic below 0
              _approveByPartition(
                  _partition,
                  msg.sender,
                  _spender,
                  _allowedByPartition[_partition][msg.sender][_spender].sub(_subtractedValue)
              );
              return true;
          }
      
          /**************************************************************************/
          /************************ Collateral Manager Admin ************************/
      
          /**
           * @notice Allow a collateral manager to self-register.
           * @dev Error 0x5c.
           */
          function registerCollateralManager() external {
              // Short circuit a double registry
              require(!_isCollateralManager[msg.sender], EC_5C_ADDRESS_CONFLICT);
      
              collateralManagers.push(msg.sender);
              _isCollateralManager[msg.sender] = true;
      
              emit CollateralManagerRegistered(msg.sender);
          }
      
          /**
           * @notice Get the status of a collateral manager.
           * @param _collateralManager The address of the collateral mananger in question.
           * @return 'true' if `_collateralManager` has self registered, 'false'
           * otherwise.
           */
          function isCollateralManager(address _collateralManager)
              external
              view
              returns (bool)
          {
              return _isCollateralManager[_collateralManager];
          }
      
          /**************************************************************************/
          /************************ Partition Strategy Admin ************************/
          /**
           * @notice Sets an implementation for a partition strategy identified by prefix.
           * @dev This is an administration method, callable only by the owner of the
           * Amp contract.
           * @param _prefix The 4 byte partition prefix the strategy applies to.
           * @param _implementation The address of the implementation of the strategy hooks.
           */
          function setPartitionStrategy(bytes4 _prefix, address _implementation) external {
              require(msg.sender == owner(), EC_56_INVALID_SENDER);
              require(!_isPartitionStrategy[_prefix], EC_5E_PARTITION_PREFIX_CONFLICT);
              require(_prefix != ZERO_PREFIX, EC_5F_INVALID_PARTITION_PREFIX_0);
      
              string memory iname = PartitionUtils._getPartitionStrategyValidatorIName(_prefix);
      
              ERC1820Client.setInterfaceImplementation(iname, _implementation);
              partitionStrategies.push(_prefix);
              _isPartitionStrategy[_prefix] = true;
      
              emit PartitionStrategySet(_prefix, iname, _implementation);
          }
      
          /**
           * @notice Return if a partition strategy has been reserved and has an
           * implementation registered.
           * @param _prefix The partition strategy identifier.
           * @return 'true' if the strategy has been registered, 'false' if not.
           */
          function isPartitionStrategy(bytes4 _prefix) external view returns (bool) {
              return _isPartitionStrategy[_prefix];
          }
      
          /**************************************************************************/
          /*************************** INTERNAL FUNCTIONS ***************************/
          /**************************************************************************/
      
          /**************************************************************************/
          /**************************** Token Transfers *****************************/
      
          /**
           * @dev Transfer tokens from a specific partition.
           * @param _fromPartition Partition of the tokens to transfer.
           * @param _operator The address performing the transfer.
           * @param _from Token holder.
           * @param _to Token recipient.
           * @param _value Number of tokens to transfer.
           * @param _data Information attached to the transfer. Contains the destination
           * partition if a partition change is requested.
           * @param _operatorData Information attached to the transfer, by the operator
           * (if any).
           * @return Destination partition.
           */
          function _transferByPartition(
              bytes32 _fromPartition,
              address _operator,
              address _from,
              address _to,
              uint256 _value,
              bytes memory _data,
              bytes memory _operatorData
          ) internal returns (bytes32) {
              require(_to != address(0), EC_57_INVALID_RECEIVER);
      
              // If the `_operator` is attempting to transfer from a different `_from`
              // address, first check that they have the requisite operator or
              // allowance permissions.
              if (_from != _operator) {
                  require(
                      _isOperatorForPartition(_fromPartition, _operator, _from) ||
                          (_value <= _allowedByPartition[_fromPartition][_from][_operator]),
                      EC_53_INSUFFICIENT_ALLOWANCE
                  );
      
                  // If the sender has an allowance for the partition, that should
                  // be decremented
                  if (_allowedByPartition[_fromPartition][_from][_operator] >= _value) {
                      _allowedByPartition[_fromPartition][_from][msg
                          .sender] = _allowedByPartition[_fromPartition][_from][_operator].sub(
                          _value
                      );
                  } else {
                      _allowedByPartition[_fromPartition][_from][_operator] = 0;
                  }
              }
      
              _callPreTransferHooks(
                  _fromPartition,
                  _operator,
                  _from,
                  _to,
                  _value,
                  _data,
                  _operatorData
              );
      
              require(
                  _balanceOfByPartition[_from][_fromPartition] >= _value,
                  EC_52_INSUFFICIENT_BALANCE
              );
      
              bytes32 toPartition = PartitionUtils._getDestinationPartition(
                  _data,
                  _fromPartition
              );
      
              _removeTokenFromPartition(_from, _fromPartition, _value);
              _addTokenToPartition(_to, toPartition, _value);
              _callPostTransferHooks(
                  toPartition,
                  _operator,
                  _from,
                  _to,
                  _value,
                  _data,
                  _operatorData
              );
      
              emit Transfer(_from, _to, _value);
              emit TransferByPartition(
                  _fromPartition,
                  _operator,
                  _from,
                  _to,
                  _value,
                  _data,
                  _operatorData
              );
      
              if (toPartition != _fromPartition) {
                  emit ChangedPartition(_fromPartition, toPartition, _value);
              }
      
              return toPartition;
          }
      
          /**
           * @notice Transfer tokens from default partitions.
           * @dev Used as a helper method for ERC20 compatibility.
           * @param _operator The address performing the transfer.
           * @param _from Token holder.
           * @param _to Token recipient.
           * @param _value Number of tokens to transfer.
           * @param _data Information attached to the transfer, and intended for the
           * token holder (`_from`). Should contain the destination partition if
           * changing partitions.
           */
          function _transferByDefaultPartition(
              address _operator,
              address _from,
              address _to,
              uint256 _value,
              bytes memory _data
          ) internal {
              _transferByPartition(defaultPartition, _operator, _from, _to, _value, _data, "");
          }
      
          /**
           * @dev Remove a token from a specific partition.
           * @param _from Token holder.
           * @param _partition Name of the partition.
           * @param _value Number of tokens to transfer.
           */
          function _removeTokenFromPartition(
              address _from,
              bytes32 _partition,
              uint256 _value
          ) internal {
              if (_value == 0) {
                  return;
              }
      
              _balances[_from] = _balances[_from].sub(_value);
      
              _balanceOfByPartition[_from][_partition] = _balanceOfByPartition[_from][_partition]
                  .sub(_value);
              totalSupplyByPartition[_partition] = totalSupplyByPartition[_partition].sub(
                  _value
              );
      
              // If the total supply is zero, finds and deletes the partition.
              // Do not delete the _defaultPartition from totalPartitions.
              if (totalSupplyByPartition[_partition] == 0 && _partition != defaultPartition) {
                  _removePartitionFromTotalPartitions(_partition);
              }
      
              // If the balance of the TokenHolder's partition is zero, finds and
              // deletes the partition.
              if (_balanceOfByPartition[_from][_partition] == 0) {
                  uint256 index = _indexOfPartitionsOf[_from][_partition];
      
                  if (index == 0) {
                      return;
                  }
      
                  // move the last item into the index being vacated
                  bytes32 lastValue = _partitionsOf[_from][_partitionsOf[_from].length - 1];
                  _partitionsOf[_from][index - 1] = lastValue; // adjust for 1-based indexing
                  _indexOfPartitionsOf[_from][lastValue] = index;
      
                  _partitionsOf[_from].pop();
                  _indexOfPartitionsOf[_from][_partition] = 0;
              }
          }
      
          /**
           * @dev Add a token to a specific partition.
           * @param _to Token recipient.
           * @param _partition Name of the partition.
           * @param _value Number of tokens to transfer.
           */
          function _addTokenToPartition(
              address _to,
              bytes32 _partition,
              uint256 _value
          ) internal {
              if (_value == 0) {
                  return;
              }
      
              _balances[_to] = _balances[_to].add(_value);
      
              if (_indexOfPartitionsOf[_to][_partition] == 0) {
                  _partitionsOf[_to].push(_partition);
                  _indexOfPartitionsOf[_to][_partition] = _partitionsOf[_to].length;
              }
              _balanceOfByPartition[_to][_partition] = _balanceOfByPartition[_to][_partition]
                  .add(_value);
      
              if (_indexOfTotalPartitions[_partition] == 0) {
                  _addPartitionToTotalPartitions(_partition);
              }
              totalSupplyByPartition[_partition] = totalSupplyByPartition[_partition].add(
                  _value
              );
          }
      
          /**
           * @dev Add a partition to the total partitions collection.
           * @param _partition Name of the partition.
           */
          function _addPartitionToTotalPartitions(bytes32 _partition) internal {
              _totalPartitions.push(_partition);
              _indexOfTotalPartitions[_partition] = _totalPartitions.length;
          }
      
          /**
           * @dev Remove a partition to the total partitions collection.
           * @param _partition Name of the partition.
           */
          function _removePartitionFromTotalPartitions(bytes32 _partition) internal {
              uint256 index = _indexOfTotalPartitions[_partition];
      
              if (index == 0) {
                  return;
              }
      
              // move the last item into the index being vacated
              bytes32 lastValue = _totalPartitions[_totalPartitions.length - 1];
              _totalPartitions[index - 1] = lastValue; // adjust for 1-based indexing
              _indexOfTotalPartitions[lastValue] = index;
      
              _totalPartitions.pop();
              _indexOfTotalPartitions[_partition] = 0;
          }
      
          /**************************************************************************/
          /********************************* Hooks **********************************/
          /**
           * @notice Check for and call the 'AmpTokensSender' hook on the sender address
           * (`_from`), and, if `_fromPartition` is within the scope of a strategy,
           * check for and call the 'AmpPartitionStrategy.tokensFromPartitionToTransfer'
           * hook for the strategy.
           * @param _fromPartition Name of the partition to transfer tokens from.
           * @param _operator Address which triggered the balance decrease (through
           * transfer).
           * @param _from Token holder.
           * @param _to Token recipient for a transfer.
           * @param _value Number of tokens the token holder balance is decreased by.
           * @param _data Extra information, pertaining to the `_from` address.
           * @param _operatorData Extra information, attached by the operator (if any).
           */
          function _callPreTransferHooks(
              bytes32 _fromPartition,
              address _operator,
              address _from,
              address _to,
              uint256 _value,
              bytes memory _data,
              bytes memory _operatorData
          ) internal {
              address senderImplementation;
              senderImplementation = interfaceAddr(_from, AMP_TOKENS_SENDER);
              if (senderImplementation != address(0)) {
                  IAmpTokensSender(senderImplementation).tokensToTransfer(
                      msg.sig,
                      _fromPartition,
                      _operator,
                      _from,
                      _to,
                      _value,
                      _data,
                      _operatorData
                  );
              }
      
              // Used to ensure that hooks implemented by a collateral manager to validate
              // transfers from it's owned partitions are called
              bytes4 fromPartitionPrefix = PartitionUtils._getPartitionPrefix(_fromPartition);
              if (_isPartitionStrategy[fromPartitionPrefix]) {
                  address fromPartitionValidatorImplementation;
                  fromPartitionValidatorImplementation = interfaceAddr(
                      address(this),
                      PartitionUtils._getPartitionStrategyValidatorIName(fromPartitionPrefix)
                  );
                  if (fromPartitionValidatorImplementation != address(0)) {
                      IAmpPartitionStrategyValidator(fromPartitionValidatorImplementation)
                          .tokensFromPartitionToValidate(
                          msg.sig,
                          _fromPartition,
                          _operator,
                          _from,
                          _to,
                          _value,
                          _data,
                          _operatorData
                      );
                  }
              }
          }
      
          /**
           * @dev Check for 'AmpTokensRecipient' hook on the recipient and call it.
           * @param _toPartition Name of the partition the tokens were transferred to.
           * @param _operator Address which triggered the balance increase (through
           * transfer or mint).
           * @param _from Token holder for a transfer (0x when mint).
           * @param _to Token recipient.
           * @param _value Number of tokens the recipient balance is increased by.
           * @param _data Extra information related to the token holder (`_from`).
           * @param _operatorData Extra information attached by the operator (if any).
           */
          function _callPostTransferHooks(
              bytes32 _toPartition,
              address _operator,
              address _from,
              address _to,
              uint256 _value,
              bytes memory _data,
              bytes memory _operatorData
          ) internal {
              bytes4 toPartitionPrefix = PartitionUtils._getPartitionPrefix(_toPartition);
              if (_isPartitionStrategy[toPartitionPrefix]) {
                  address partitionManagerImplementation;
                  partitionManagerImplementation = interfaceAddr(
                      address(this),
                      PartitionUtils._getPartitionStrategyValidatorIName(toPartitionPrefix)
                  );
                  if (partitionManagerImplementation != address(0)) {
                      IAmpPartitionStrategyValidator(partitionManagerImplementation)
                          .tokensToPartitionToValidate(
                          msg.sig,
                          _toPartition,
                          _operator,
                          _from,
                          _to,
                          _value,
                          _data,
                          _operatorData
                      );
                  }
              } else {
                  require(toPartitionPrefix == ZERO_PREFIX, EC_5D_PARTITION_RESERVED);
              }
      
              address recipientImplementation;
              recipientImplementation = interfaceAddr(_to, AMP_TOKENS_RECIPIENT);
      
              if (recipientImplementation != address(0)) {
                  IAmpTokensRecipient(recipientImplementation).tokensReceived(
                      msg.sig,
                      _toPartition,
                      _operator,
                      _from,
                      _to,
                      _value,
                      _data,
                      _operatorData
                  );
              }
          }
      
          /**************************************************************************/
          /******************************* Allowance ********************************/
          /**
           * @notice Approve the `_spender` address to spend the specified amount of
           * tokens in `_partition` on behalf of 'msg.sender'.
           * @param _partition Name of the partition.
           * @param _tokenHolder Owner of the tokens.
           * @param _spender The address which will spend the tokens.
           * @param _amount The amount of tokens to be tokens.
           */
          function _approveByPartition(
              bytes32 _partition,
              address _tokenHolder,
              address _spender,
              uint256 _amount
          ) internal {
              require(_tokenHolder != address(0), EC_56_INVALID_SENDER);
              require(_spender != address(0), EC_58_INVALID_OPERATOR);
      
              _allowedByPartition[_partition][_tokenHolder][_spender] = _amount;
              emit ApprovalByPartition(_partition, _tokenHolder, _spender, _amount);
      
              if (_partition == defaultPartition) {
                  emit Approval(_tokenHolder, _spender, _amount);
              }
          }
      
          /**************************************************************************/
          /************************** Operator Information **************************/
          /**
           * @dev Indicate whether the operator address is an operator of the
           * tokenHolder address. An operator in this case is an operator across all
           * partitions of the `msg.sender` address.
           * @param _operator Address which may be an operator of '_tokenHolder'.
           * @param _tokenHolder Address of a token holder which may have the '_operator'
           * address as an operator.
           * @return 'true' if `_operator` is an operator of `_tokenHolder` and 'false'
           * otherwise.
           */
          function _isOperator(address _operator, address _tokenHolder)
              internal
              view
              returns (bool)
          {
              return (_operator == _tokenHolder ||
                  _authorizedOperator[_tokenHolder][_operator]);
          }
      
          /**
           * @dev Indicate whether the operator address is an operator of the
           * tokenHolder address for the given partition.
           * @param _partition Name of the partition.
           * @param _operator Address which may be an operator of tokenHolder for the
           * given partition.
           * @param _tokenHolder Address of a token holder which may have the operator
           * address as an operator for the given partition.
           * @return 'true' if 'operator' is an operator of 'tokenHolder' for partition
           * `_partition` and 'false' otherwise.
           */
          function _isOperatorForPartition(
              bytes32 _partition,
              address _operator,
              address _tokenHolder
          ) internal view returns (bool) {
              return (_isOperator(_operator, _tokenHolder) ||
                  _authorizedOperatorByPartition[_tokenHolder][_partition][_operator] ||
                  _callPartitionStrategyOperatorHook(_partition, _operator, _tokenHolder));
          }
      
          /**
           * @notice Check if the `_partition` is within the scope of a strategy, and
           * call it's isOperatorForPartitionScope hook if so.
           * @dev This allows implicit granting of operatorByPartition permissions
           * based on the partition being used being of a strategy.
           * @param _partition The partition to check.
           * @param _operator The address to check if is an operator for `_tokenHolder`.
           * @param _tokenHolder The address to validate that `_operator` is an
           * operator for.
           */
          function _callPartitionStrategyOperatorHook(
              bytes32 _partition,
              address _operator,
              address _tokenHolder
          ) internal view returns (bool) {
              bytes4 prefix = PartitionUtils._getPartitionPrefix(_partition);
      
              if (!_isPartitionStrategy[prefix]) {
                  return false;
              }
      
              address strategyValidatorImplementation;
              strategyValidatorImplementation = interfaceAddr(
                  address(this),
                  PartitionUtils._getPartitionStrategyValidatorIName(prefix)
              );
              if (strategyValidatorImplementation != address(0)) {
                  return
                      IAmpPartitionStrategyValidator(strategyValidatorImplementation)
                          .isOperatorForPartitionScope(_partition, _operator, _tokenHolder);
              }
      
              // Not a partition format that imbues special operator rules
              return false;
          }
      
          /**************************************************************************/
          /******************************** Minting *********************************/
          /**
           * @notice Perform the minting of tokens.
           * @dev The tokens will be minted on behalf of the `_to` address, and will be
           * minted to the address's default partition.
           * @param _operator Address which triggered the issuance.
           * @param _to Token recipient.
           * @param _value Number of tokens issued.
           */
          function _mint(
              address _operator,
              address _to,
              uint256 _value
          ) internal {
              require(_to != address(0), EC_57_INVALID_RECEIVER);
      
              _totalSupply = _totalSupply.add(_value);
              _addTokenToPartition(_to, defaultPartition, _value);
              _callPostTransferHooks(
                  defaultPartition,
                  _operator,
                  address(0),
                  _to,
                  _value,
                  "",
                  ""
              );
      
              emit Minted(_operator, _to, _value, "");
              emit Transfer(address(0), _to, _value);
              emit TransferByPartition(bytes32(0), _operator, address(0), _to, _value, "", "");
          }
      }

      File 2 of 4: FlexaCollateralManager
      // SPDX-License-Identifier: MIT
      
      pragma solidity 0.6.10;
      
      /**
       * @dev Wrappers over Solidity's arithmetic operations with added overflow
       * checks.
       *
       * Arithmetic operations in Solidity wrap on overflow. This can easily result
       * in bugs, because programmers usually assume that an overflow raises an
       * error, which is the standard behavior in high level programming languages.
       * `SafeMath` restores this intuition by reverting the transaction when an
       * operation overflows.
       *
       * Using this library instead of the unchecked operations eliminates an entire
       * class of bugs, so it's recommended to use it always.
       */
      library SafeMath {
          /**
           * @dev Returns the addition of two unsigned integers, reverting on
           * overflow.
           *
           * Counterpart to Solidity's `+` operator.
           *
           * Requirements:
           * - Addition cannot overflow.
           */
          function add(uint256 a, uint256 b) internal pure returns (uint256) {
              uint256 c = a + b;
              require(c >= a, "SafeMath: addition overflow");
      
              return c;
          }
      
          /**
           * @dev Returns the subtraction of two unsigned integers, reverting on
           * overflow (when the result is negative).
           *
           * Counterpart to Solidity's `-` operator.
           *
           * Requirements:
           * - Subtraction cannot overflow.
           */
          function sub(uint256 a, uint256 b) internal pure returns (uint256) {
              return sub(a, b, "SafeMath: subtraction overflow");
          }
      
          /**
           * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
           * overflow (when the result is negative).
           *
           * Counterpart to Solidity's `-` operator.
           *
           * Requirements:
           * - Subtraction cannot overflow.
           */
          function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
              require(b <= a, errorMessage);
              uint256 c = a - b;
      
              return c;
          }
      
          /**
           * @dev Returns the multiplication of two unsigned integers, reverting on
           * overflow.
           *
           * Counterpart to Solidity's `*` operator.
           *
           * Requirements:
           * - Multiplication cannot overflow.
           */
          function mul(uint256 a, uint256 b) internal pure returns (uint256) {
              // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
              // benefit is lost if 'b' is also tested.
              // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
              if (a == 0) {
                  return 0;
              }
      
              uint256 c = a * b;
              require(c / a == b, "SafeMath: multiplication overflow");
      
              return c;
          }
      
          /**
           * @dev Returns the integer division of two unsigned integers. Reverts on
           * division by zero. The result is rounded towards zero.
           *
           * Counterpart to Solidity's `/` operator. Note: this function uses a
           * `revert` opcode (which leaves remaining gas untouched) while Solidity
           * uses an invalid opcode to revert (consuming all remaining gas).
           *
           * Requirements:
           * - The divisor cannot be zero.
           */
          function div(uint256 a, uint256 b) internal pure returns (uint256) {
              return div(a, b, "SafeMath: division by zero");
          }
      
          /**
           * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
           * division by zero. The result is rounded towards zero.
           *
           * Counterpart to Solidity's `/` operator. Note: this function uses a
           * `revert` opcode (which leaves remaining gas untouched) while Solidity
           * uses an invalid opcode to revert (consuming all remaining gas).
           *
           * Requirements:
           * - The divisor cannot be zero.
           */
          function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
              // Solidity only automatically asserts when dividing by 0
              require(b > 0, errorMessage);
              uint256 c = a / b;
              // assert(a == b * c + a % b); // There is no case in which this doesn't hold
      
              return c;
          }
      
          /**
           * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
           * Reverts when dividing by zero.
           *
           * Counterpart to Solidity's `%` operator. This function uses a `revert`
           * opcode (which leaves remaining gas untouched) while Solidity uses an
           * invalid opcode to revert (consuming all remaining gas).
           *
           * Requirements:
           * - The divisor cannot be zero.
           */
          function mod(uint256 a, uint256 b) internal pure returns (uint256) {
              return mod(a, b, "SafeMath: modulo by zero");
          }
      
          /**
           * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
           * Reverts with custom message when dividing by zero.
           *
           * Counterpart to Solidity's `%` operator. This function uses a `revert`
           * opcode (which leaves remaining gas untouched) while Solidity uses an
           * invalid opcode to revert (consuming all remaining gas).
           *
           * Requirements:
           * - The divisor cannot be zero.
           */
          function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
              require(b != 0, errorMessage);
              return a % b;
          }
      }
      
      interface IAmp {
          function registerCollateralManager() external;
      }
      
      /**
       * @title Ownable is a contract the provides contract ownership functionality, including a two-
       * phase transfer.
       */
      contract Ownable {
          address private _owner;
          address private _authorizedNewOwner;
      
          /**
           * @notice Emitted when the owner authorizes ownership transfer to a new address
           * @param authorizedAddress New owner address
           */
          event OwnershipTransferAuthorization(address indexed authorizedAddress);
      
          /**
           * @notice Emitted when the authorized address assumed ownership
           * @param oldValue Old owner
           * @param newValue New owner
           */
          event OwnerUpdate(address indexed oldValue, address indexed newValue);
      
          /**
           * @notice Sets the owner to the sender / contract creator
           */
          constructor() internal {
              _owner = msg.sender;
          }
      
          /**
           * @notice Retrieves the owner of the contract
           * @return The contract owner
           */
          function owner() public view returns (address) {
              return _owner;
          }
      
          /**
           * @notice Retrieves the authorized new owner of the contract
           * @return The authorized new contract owner
           */
          function authorizedNewOwner() public view returns (address) {
              return _authorizedNewOwner;
          }
      
          /**
           * @notice Authorizes the transfer of ownership from owner to the provided address.
           * NOTE: No transfer will occur unless authorizedAddress calls assumeOwnership().
           * This authorization may be removed by another call to this function authorizing the zero
           * address.
           * @param _authorizedAddress The address authorized to become the new owner
           */
          function authorizeOwnershipTransfer(address _authorizedAddress) external {
              require(msg.sender == _owner, "Invalid sender");
      
              _authorizedNewOwner = _authorizedAddress;
      
              emit OwnershipTransferAuthorization(_authorizedNewOwner);
          }
      
          /**
           * @notice Transfers ownership of this contract to the _authorizedNewOwner
           * @dev Error invalid sender.
           */
          function assumeOwnership() external {
              require(msg.sender == _authorizedNewOwner, "Invalid sender");
      
              address oldValue = _owner;
              _owner = _authorizedNewOwner;
              _authorizedNewOwner = address(0);
      
              emit OwnerUpdate(oldValue, _owner);
          }
      }
      
      abstract contract ERC1820Registry {
          function setInterfaceImplementer(
              address _addr,
              bytes32 _interfaceHash,
              address _implementer
          ) external virtual;
      
          function getInterfaceImplementer(address _addr, bytes32 _interfaceHash)
              external
              virtual
              view
              returns (address);
      
          function setManager(address _addr, address _newManager) external virtual;
      
          function getManager(address _addr) public virtual view returns (address);
      }
      
      /// Base client to interact with the registry.
      contract ERC1820Client {
          ERC1820Registry constant ERC1820REGISTRY = ERC1820Registry(
              0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
          );
      
          function setInterfaceImplementation(
              string memory _interfaceLabel,
              address _implementation
          ) internal {
              bytes32 interfaceHash = keccak256(abi.encodePacked(_interfaceLabel));
              ERC1820REGISTRY.setInterfaceImplementer(
                  address(this),
                  interfaceHash,
                  _implementation
              );
          }
      
          function interfaceAddr(address addr, string memory _interfaceLabel)
              internal
              view
              returns (address)
          {
              bytes32 interfaceHash = keccak256(abi.encodePacked(_interfaceLabel));
              return ERC1820REGISTRY.getInterfaceImplementer(addr, interfaceHash);
          }
      
          function delegateManagement(address _newManager) internal {
              ERC1820REGISTRY.setManager(address(this), _newManager);
          }
      }
      
      /**
       * @title IAmpTokensRecipient
       * @dev IAmpTokensRecipient token transfer hook interface
       */
      interface IAmpTokensRecipient {
          /**
           * @dev Report if the recipient will successfully receive the tokens
           */
          function canReceive(
              bytes4 functionSig,
              bytes32 partition,
              address operator,
              address from,
              address to,
              uint256 value,
              bytes calldata data,
              bytes calldata operatorData
          ) external view returns (bool);
      
          /**
           * @dev Hook executed upon a transfer to the recipient
           */
          function tokensReceived(
              bytes4 functionSig,
              bytes32 partition,
              address operator,
              address from,
              address to,
              uint256 value,
              bytes calldata data,
              bytes calldata operatorData
          ) external;
      }
      
      /**
       * @title IAmpTokensSender
       * @dev IAmpTokensSender token transfer hook interface
       */
      interface IAmpTokensSender {
          /**
           * @dev Report if the transfer will succeed from the pespective of the
           * token sender
           */
          function canTransfer(
              bytes4 functionSig,
              bytes32 partition,
              address operator,
              address from,
              address to,
              uint256 value,
              bytes calldata data,
              bytes calldata operatorData
          ) external view returns (bool);
      
          /**
           * @dev Hook executed upon a transfer on behalf of the sender
           */
          function tokensToTransfer(
              bytes4 functionSig,
              bytes32 partition,
              address operator,
              address from,
              address to,
              uint256 value,
              bytes calldata data,
              bytes calldata operatorData
          ) external;
      }
      
      /**
       * @title PartitionUtils
       * @notice Partition related helper functions.
       */
      
      library PartitionUtils {
          bytes32 public constant CHANGE_PARTITION_FLAG = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
      
          /**
           * @notice Retrieve the destination partition from the 'data' field.
           * A partition change is requested ONLY when 'data' starts with the flag:
           *
           *   0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
           *
           * When the flag is detected, the destination partition is extracted from the
           * 32 bytes following the flag.
           * @param _data Information attached to the transfer. Will contain the
           * destination partition if a change is requested.
           * @param _fallbackPartition Partition value to return if a partition change
           * is not requested in the `_data`.
           * @return toPartition Destination partition. If the `_data` does not contain
           * the prefix and bytes32 partition in the first 64 bytes, the method will
           * return the provided `_fromPartition`.
           */
          function _getDestinationPartition(bytes memory _data, bytes32 _fallbackPartition)
              internal
              pure
              returns (bytes32)
          {
              if (_data.length < 64) {
                  return _fallbackPartition;
              }
      
              (bytes32 flag, bytes32 toPartition) = abi.decode(_data, (bytes32, bytes32));
              if (flag == CHANGE_PARTITION_FLAG) {
                  return toPartition;
              }
      
              return _fallbackPartition;
          }
      
          /**
           * @notice Helper to get the strategy identifying prefix from the `_partition`.
           * @param _partition Partition to get the prefix for.
           * @return 4 byte partition strategy prefix.
           */
          function _getPartitionPrefix(bytes32 _partition) internal pure returns (bytes4) {
              return bytes4(_partition);
          }
      
          /**
           * @notice Helper method to split the partition into the prefix, sub partition
           * and partition owner components.
           * @param _partition The partition to split into parts.
           * @return The 4 byte partition prefix, 8 byte sub partition, and final 20
           * bytes representing an address.
           */
          function _splitPartition(bytes32 _partition)
              internal
              pure
              returns (
                  bytes4,
                  bytes8,
                  address
              )
          {
              bytes4 prefix = bytes4(_partition);
              bytes8 subPartition = bytes8(_partition << 32);
              address addressPart = address(uint160(uint256(_partition)));
              return (prefix, subPartition, addressPart);
          }
      
          /**
           * @notice Helper method to get a partition strategy ERC1820 interface name
           * based on partition prefix.
           * @param _prefix 4 byte partition prefix.
           * @dev Each 4 byte prefix has a unique interface name so that an individual
           * hook implementation can be set for each prefix.
           */
          function _getPartitionStrategyValidatorIName(bytes4 _prefix)
              internal
              pure
              returns (string memory)
          {
              return string(abi.encodePacked("AmpPartitionStrategyValidator", _prefix));
          }
      }
      
      /**
       * @title FlexaCollateralManager is an implementation of IAmpTokensSender and IAmpTokensRecipient
       * which serves as the Amp collateral manager for the Flexa Network.
       */
      contract FlexaCollateralManager is Ownable, IAmpTokensSender, IAmpTokensRecipient, ERC1820Client {
          /**
           * @dev AmpTokensSender interface label.
           */
          string internal constant AMP_TOKENS_SENDER = "AmpTokensSender";
      
          /**
           * @dev AmpTokensRecipient interface label.
           */
          string internal constant AMP_TOKENS_RECIPIENT = "AmpTokensRecipient";
      
          /**
           * @dev Change Partition Flag used in transfer data parameters to signal which partition
           * will receive the tokens.
           */
          bytes32
              internal constant CHANGE_PARTITION_FLAG = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
      
          /**
           * @dev Required prefix for all registered partitions. Used to ensure the Collateral Pool
           * Partition Validator is used within Amp.
           */
          bytes4 internal constant PARTITION_PREFIX = 0xCCCCCCCC;
      
          /**********************************************************************************************
           * Operator Data Flags
           *********************************************************************************************/
      
          /**
           * @dev Flag used in operator data parameters to indicate the transfer is a withdrawal
           */
          bytes32
              internal constant WITHDRAWAL_FLAG = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa;
      
          /**
           * @dev Flag used in operator data parameters to indicate the transfer is a fallback
           * withdrawal
           */
          bytes32
              internal constant FALLBACK_WITHDRAWAL_FLAG = 0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb;
      
          /**
           * @dev Flag used in operator data parameters to indicate the transfer is a supply refund
           */
          bytes32
              internal constant REFUND_FLAG = 0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc;
      
          /**
           * @dev Flag used in operator data parameters to indicate the transfer is a direct transfer
           */
          bytes32
              internal constant DIRECT_TRANSFER_FLAG = 0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd;
      
          /**********************************************************************************************
           * Configuration
           *********************************************************************************************/
      
          /**
           * @notice Address of the Amp contract. Immutable.
           */
          address public amp;
      
          /**
           * @notice Permitted partitions
           */
          mapping(bytes32 => bool) public partitions;
      
          /**********************************************************************************************
           * Roles
           *********************************************************************************************/
      
          /**
           * @notice Address authorized to publish withdrawal roots
           */
          address public withdrawalPublisher;
      
          /**
           * @notice Address authorized to publish fallback withdrawal roots
           */
          address public fallbackPublisher;
      
          /**
           * @notice Address authorized to adjust the withdrawal limit
           */
          address public withdrawalLimitPublisher;
      
          /**
           * @notice Address authorized to directly transfer tokens
           */
          address public directTransferer;
      
          /**
           * @notice Address authorized to manage permitted partition
           */
          address public partitionManager;
      
          /**
           * @notice Struct used to record received tokens that can be recovered during the fallback
           * withdrawal period
           * @param supplier Token supplier
           * @param partition Partition which received the tokens
           * @param amount Number of tokens received
           */
          struct Supply {
              address supplier;
              bytes32 partition;
              uint256 amount;
          }
      
          /**********************************************************************************************
           * Supply State
           *********************************************************************************************/
      
          /**
           * @notice Supply nonce used to track incoming token transfers
           */
          uint256 public supplyNonce = 0;
      
          /**
           * @notice Mapping of all incoming token transfers
           */
          mapping(uint256 => Supply) public nonceToSupply;
      
          /**********************************************************************************************
           * Withdrawal State
           *********************************************************************************************/
      
          /**
           * @notice Remaining withdrawal limit. Initially set to 100,000 Amp.
           */
          uint256 public withdrawalLimit = 100 * 1000 * (10**18);
      
          /**
           * @notice Withdrawal maximum root nonce
           */
          uint256 public maxWithdrawalRootNonce = 0;
      
          /**
           * @notice Active set of withdrawal roots
           */
          mapping(bytes32 => uint256) public withdrawalRootToNonce;
      
          /**
           * @notice Last invoked withdrawal root for each account, per partition
           */
          mapping(bytes32 => mapping(address => uint256)) public addressToWithdrawalNonce;
      
          /**
           * @notice Total amount withdrawn for each account, per partition
           */
          mapping(bytes32 => mapping(address => uint256)) public addressToCumulativeAmountWithdrawn;
      
          /**********************************************************************************************
           * Fallback Withdrawal State
           *********************************************************************************************/
      
          /**
           * @notice Withdrawal fallback delay. Initially set to one week.
           */
          uint256 public fallbackWithdrawalDelaySeconds = 1 weeks;
      
          /**
           * @notice Current fallback withdrawal root
           */
          bytes32 public fallbackRoot;
      
          /**
           * @notice Timestamp of when the last fallback root was published
           */
          uint256 public fallbackSetDate = 2**200; // very far in the future
      
          /**
           * @notice Latest supply reflected in the fallback withdrawal authorization tree
           */
          uint256 public fallbackMaxIncludedSupplyNonce = 0;
      
          /**********************************************************************************************
           * Supplier Events
           *********************************************************************************************/
      
          /**
           * @notice Indicates a token supply has been received
           * @param supplier Token supplier
           * @param amount Number of tokens transferred
           * @param nonce Nonce of the supply
           */
          event SupplyReceipt(
              address indexed supplier,
              bytes32 indexed partition,
              uint256 amount,
              uint256 indexed nonce
          );
      
          /**
           * @notice Indicates that a withdrawal was executed
           * @param supplier Address whose withdrawal authorization was executed
           * @param partition Partition from which the tokens were transferred
           * @param amount Amount of tokens transferred
           * @param rootNonce Nonce of the withdrawal root used for authorization
           * @param authorizedAccountNonce Maximum previous nonce used by the account
           */
          event Withdrawal(
              address indexed supplier,
              bytes32 indexed partition,
              uint256 amount,
              uint256 indexed rootNonce,
              uint256 authorizedAccountNonce
          );
      
          /**
           * @notice Indicates a fallback withdrawal was executed
           * @param supplier Address whose fallback withdrawal authorization was executed
           * @param partition Partition from which the tokens were transferred
           * @param amount Amount of tokens transferred
           */
          event FallbackWithdrawal(
              address indexed supplier,
              bytes32 indexed partition,
              uint256 indexed amount
          );
      
          /**
           * @notice Indicates a release of supply is requested
           * @param supplier Token supplier
           * @param partition Parition from which the tokens should be released
           * @param amount Number of tokens requested to be released
           * @param data Metadata provided by the requestor
           */
          event ReleaseRequest(
              address indexed supplier,
              bytes32 indexed partition,
              uint256 indexed amount,
              bytes data
          );
      
          /**
           * @notice Indicates a supply refund was executed
           * @param supplier Address whose refund authorization was executed
           * @param partition Partition from which the tokens were transferred
           * @param amount Amount of tokens transferred
           * @param nonce Nonce of the original supply
           */
          event SupplyRefund(
              address indexed supplier,
              bytes32 indexed partition,
              uint256 amount,
              uint256 indexed nonce
          );
      
          /**********************************************************************************************
           * Direct Transfer Events
           *********************************************************************************************/
      
          /**
           * @notice Emitted when tokens are directly transfered
           * @param operator Address that executed the direct transfer
           * @param from_partition Partition from which the tokens were transferred
           * @param to_address Address to which the tokens were transferred
           * @param to_partition Partition to which the tokens were transferred
           * @param value Amount of tokens transferred
           */
          event DirectTransfer(
              address operator,
              bytes32 indexed from_partition,
              address indexed to_address,
              bytes32 indexed to_partition,
              uint256 value
          );
      
          /**********************************************************************************************
           * Admin Configuration Events
           *********************************************************************************************/
      
          /**
           * @notice Emitted when a partition is permitted for supply
           * @param partition Partition added to the permitted set
           */
          event PartitionAdded(bytes32 indexed partition);
      
          /**
           * @notice Emitted when a partition is removed from the set permitted for supply
           * @param partition Partition removed from the permitted set
           */
          event PartitionRemoved(bytes32 indexed partition);
      
          /**********************************************************************************************
           * Admin Withdrawal Management Events
           *********************************************************************************************/
      
          /**
           * @notice Emitted when a new withdrawal root hash is added to the active set
           * @param rootHash Merkle root hash.
           * @param nonce Nonce of the Merkle root hash.
           */
          event WithdrawalRootHashAddition(bytes32 indexed rootHash, uint256 indexed nonce);
      
          /**
           * @notice Emitted when a withdrawal root hash is removed from the active set
           * @param rootHash Merkle root hash.
           * @param nonce Nonce of the Merkle root hash.
           */
          event WithdrawalRootHashRemoval(bytes32 indexed rootHash, uint256 indexed nonce);
      
          /**
           * @notice Emitted when the withdrawal limit is updated
           * @param oldValue Old limit.
           * @param newValue New limit.
           */
          event WithdrawalLimitUpdate(uint256 indexed oldValue, uint256 indexed newValue);
      
          /**********************************************************************************************
           * Admin Fallback Management Events
           *********************************************************************************************/
      
          /**
           * @notice Emitted when a new fallback withdrawal root hash is set
           * @param rootHash Merkle root hash
           * @param maxSupplyNonceIncluded Nonce of the last supply reflected in the tree data
           * @param setDate Timestamp of when the root hash was set
           */
          event FallbackRootHashSet(
              bytes32 indexed rootHash,
              uint256 indexed maxSupplyNonceIncluded,
              uint256 setDate
          );
      
          /**
           * @notice Emitted when the fallback root hash set date is reset
           * @param newDate Timestamp of when the fallback reset date was set
           */
          event FallbackMechanismDateReset(uint256 indexed newDate);
      
          /**
           * @notice Emitted when the fallback delay is updated
           * @param oldValue Old delay
           * @param newValue New delay
           */
          event FallbackWithdrawalDelayUpdate(uint256 indexed oldValue, uint256 indexed newValue);
      
          /**********************************************************************************************
           * Role Management Events
           *********************************************************************************************/
      
          /**
           * @notice Emitted when the Withdrawal Publisher is updated
           * @param oldValue Old publisher
           * @param newValue New publisher
           */
          event WithdrawalPublisherUpdate(address indexed oldValue, address indexed newValue);
      
          /**
           * @notice Emitted when the Fallback Publisher is updated
           * @param oldValue Old publisher
           * @param newValue New publisher
           */
          event FallbackPublisherUpdate(address indexed oldValue, address indexed newValue);
      
          /**
           * @notice Emitted when Withdrawal Limit Publisher is updated
           * @param oldValue Old publisher
           * @param newValue New publisher
           */
          event WithdrawalLimitPublisherUpdate(address indexed oldValue, address indexed newValue);
      
          /**
           * @notice Emitted when the DirectTransferer address is updated
           * @param oldValue Old DirectTransferer address
           * @param newValue New DirectTransferer address
           */
          event DirectTransfererUpdate(address indexed oldValue, address indexed newValue);
      
          /**
           * @notice Emitted when the Partition Manager address is updated
           * @param oldValue Old Partition Manager address
           * @param newValue New Partition Manager address
           */
          event PartitionManagerUpdate(address indexed oldValue, address indexed newValue);
      
          /**********************************************************************************************
           * Constructor
           *********************************************************************************************/
      
          /**
           * @notice FlexaCollateralManager constructor
           * @param _amp Address of the Amp token contract
           */
          constructor(address _amp) public {
              amp = _amp;
      
              ERC1820Client.setInterfaceImplementation(AMP_TOKENS_RECIPIENT, address(this));
              ERC1820Client.setInterfaceImplementation(AMP_TOKENS_SENDER, address(this));
      
              IAmp(amp).registerCollateralManager();
          }
      
          /**********************************************************************************************
           * IAmpTokensRecipient Hooks
           *********************************************************************************************/
      
          /**
           * @notice Validates where the supplied parameters are valid for a transfer of tokens to this
           * contract
           * @dev Implements IAmpTokensRecipient
           * @param _partition Partition from which the tokens were transferred
           * @param _to The destination address of the tokens. Must be this.
           * @param _data Optional data sent with the transfer. Used to set the destination partition.
           * @return true if the tokens can be received, otherwise false
           */
          function canReceive(
              bytes4, /* functionSig */
              bytes32 _partition,
              address, /* operator */
              address, /* from */
              address _to,
              uint256, /* value */
              bytes calldata _data,
              bytes calldata /* operatorData */
          ) external override view returns (bool) {
              if (msg.sender != amp || _to != address(this)) {
                  return false;
              }
      
              bytes32 _destinationPartition = PartitionUtils._getDestinationPartition(_data, _partition);
      
              return partitions[_destinationPartition];
          }
      
          /**
           * @notice Function called by the token contract after executing a transfer.
           * @dev Implements IAmpTokensRecipient
           * @param _partition Partition from which the tokens were transferred
           * @param _operator Address which triggered the transfer. This address will be credited with
           * the supply.
           * @param _to The destination address of the tokens. Must be this.
           * @param _value Number of tokens the token holder balance is decreased by.
           * @param _data Optional data sent with the transfer. Used to set the destination partition.
           */
          function tokensReceived(
              bytes4, /* functionSig */
              bytes32 _partition,
              address _operator,
              address, /* from */
              address _to,
              uint256 _value,
              bytes calldata _data,
              bytes calldata /* operatorData */
          ) external override {
              require(msg.sender == amp, "Invalid sender");
              require(_to == address(this), "Invalid to address");
      
              bytes32 _destinationPartition = PartitionUtils._getDestinationPartition(_data, _partition);
      
              require(partitions[_destinationPartition], "Invalid destination partition");
      
              supplyNonce = SafeMath.add(supplyNonce, 1);
              nonceToSupply[supplyNonce].supplier = _operator;
              nonceToSupply[supplyNonce].partition = _destinationPartition;
              nonceToSupply[supplyNonce].amount = _value;
      
              emit SupplyReceipt(_operator, _destinationPartition, _value, supplyNonce);
          }
      
          /**********************************************************************************************
           * IAmpTokensSender Hooks
           *********************************************************************************************/
      
          /**
           * @notice Validates where the supplied parameters are valid for a transfer of tokens from this
           * contract
           * @dev Implements IAmpTokensSender
           * @param _partition Source partition of the tokens
           * @param _operator Address which triggered the transfer
           * @param _from The source address of the tokens. Must be this.
           * @param _value Amount of tokens to be transferred
           * @param _operatorData Extra information attached by the operator. Must include the transfer
           * operation flag and additional authorization data custom for each transfer operation type.
           * @return true if the token transfer would succeed, otherwise false
           */
          function canTransfer(
              bytes4, /*functionSig*/
              bytes32 _partition,
              address _operator,
              address _from,
              address, /* to */
              uint256 _value,
              bytes calldata, /* data */
              bytes calldata _operatorData
          ) external override view returns (bool) {
              if (msg.sender != amp || _from != address(this)) {
                  return false;
              }
      
              bytes32 flag = _decodeOperatorDataFlag(_operatorData);
      
              if (flag == WITHDRAWAL_FLAG) {
                  return _validateWithdrawal(_partition, _operator, _value, _operatorData);
              }
              if (flag == FALLBACK_WITHDRAWAL_FLAG) {
                  return _validateFallbackWithdrawal(_partition, _operator, _value, _operatorData);
              }
              if (flag == REFUND_FLAG) {
                  return _validateRefund(_partition, _operator, _value, _operatorData);
              }
              if (flag == DIRECT_TRANSFER_FLAG) {
                  return _validateDirectTransfer(_operator, _value);
              }
      
              return false;
          }
      
          /**
           * @notice Function called by the token contract when executing a transfer
           * @dev Implements IAmpTokensSender
           * @param _partition Source partition of the tokens
           * @param _operator Address which triggered the transfer
           * @param _from The source address of the tokens. Must be this.
           * @param _to The target address of the tokens.
           * @param _value Amount of tokens to be transferred
           * @param _data Data attached to the transfer. Typically includes partition change information.
           * @param _operatorData Extra information attached by the operator. Must include the transfer
           * operation flag and additional authorization data custom for each transfer operation type.
           */
          function tokensToTransfer(
              bytes4, /* functionSig */
              bytes32 _partition,
              address _operator,
              address _from,
              address _to,
              uint256 _value,
              bytes calldata _data,
              bytes calldata _operatorData
          ) external override {
              require(msg.sender == amp, "Invalid sender");
              require(_from == address(this), "Invalid from address");
      
              bytes32 flag = _decodeOperatorDataFlag(_operatorData);
      
              if (flag == WITHDRAWAL_FLAG) {
                  _executeWithdrawal(_partition, _operator, _value, _operatorData);
              } else if (flag == FALLBACK_WITHDRAWAL_FLAG) {
                  _executeFallbackWithdrawal(_partition, _operator, _value, _operatorData);
              } else if (flag == REFUND_FLAG) {
                  _executeRefund(_partition, _operator, _value, _operatorData);
              } else if (flag == DIRECT_TRANSFER_FLAG) {
                  _executeDirectTransfer(_partition, _operator, _to, _value, _data);
              } else {
                  revert("invalid flag");
              }
          }
      
          /**********************************************************************************************
           * Withdrawals
           *********************************************************************************************/
      
          /**
           * @notice Validates withdrawal data
           * @param _partition Source partition of the withdrawal
           * @param _operator Address that is invoking the transfer
           * @param _value Number of tokens to be transferred
           * @param _operatorData Contains the withdrawal authorization data
           * @return true if the withdrawal data is valid, otherwise false
           */
          function _validateWithdrawal(
              bytes32 _partition,
              address _operator,
              uint256 _value,
              bytes memory _operatorData
          ) internal view returns (bool) {
              (
                  address supplier,
                  uint256 maxAuthorizedAccountNonce,
                  uint256 withdrawalRootNonce
              ) = _getWithdrawalData(_partition, _value, _operatorData);
      
              return
                  _validateWithdrawalData(
                      _partition,
                      _operator,
                      _value,
                      supplier,
                      maxAuthorizedAccountNonce,
                      withdrawalRootNonce
                  );
          }
      
          /**
           * @notice Validates the withdrawal data and updates state to reflect the transfer
           * @param _partition Source partition of the withdrawal
           * @param _operator Address that is invoking the transfer
           * @param _value Number of tokens to be transferred
           * @param _operatorData Contains the withdrawal authorization data
           */
          function _executeWithdrawal(
              bytes32 _partition,
              address _operator,
              uint256 _value,
              bytes memory _operatorData
          ) internal {
              (
                  address supplier,
                  uint256 maxAuthorizedAccountNonce,
                  uint256 withdrawalRootNonce
              ) = _getWithdrawalData(_partition, _value, _operatorData);
      
              require(
                  _validateWithdrawalData(
                      _partition,
                      _operator,
                      _value,
                      supplier,
                      maxAuthorizedAccountNonce,
                      withdrawalRootNonce
                  ),
                  "Transfer unauthorized"
              );
      
              addressToCumulativeAmountWithdrawn[_partition][supplier] = SafeMath.add(
                  _value,
                  addressToCumulativeAmountWithdrawn[_partition][supplier]
              );
      
              addressToWithdrawalNonce[_partition][supplier] = withdrawalRootNonce;
      
              withdrawalLimit = SafeMath.sub(withdrawalLimit, _value);
      
              emit Withdrawal(
                  supplier,
                  _partition,
                  _value,
                  withdrawalRootNonce,
                  maxAuthorizedAccountNonce
              );
          }
      
          /**
           * @notice Extracts withdrawal data from the supplied parameters
           * @param _partition Source partition of the withdrawal
           * @param _value Number of tokens to be transferred
           * @param _operatorData Contains the withdrawal authorization data, including the withdrawal
           * operation flag, supplier, maximum authorized account nonce, and Merkle proof.
           * @return supplier, the address whose account is authorized
           * @return maxAuthorizedAccountNonce, the maximum existing used withdrawal nonce for the
           * supplier and partition
           * @return withdrawalRootNonce, the active withdrawal root nonce found based on the supplied
           * data and Merkle proof
           */
          function _getWithdrawalData(
              bytes32 _partition,
              uint256 _value,
              bytes memory _operatorData
          )
              internal
              view
              returns (
                  address, /* supplier */
                  uint256, /* maxAuthorizedAccountNonce */
                  uint256 /* withdrawalRootNonce */
              )
          {
              (
                  address supplier,
                  uint256 maxAuthorizedAccountNonce,
                  bytes32[] memory merkleProof
              ) = _decodeWithdrawalOperatorData(_operatorData);
      
              bytes32 leafDataHash = _calculateWithdrawalLeaf(
                  supplier,
                  _partition,
                  _value,
                  maxAuthorizedAccountNonce
              );
      
              bytes32 calculatedRoot = _calculateMerkleRoot(merkleProof, leafDataHash);
              uint256 withdrawalRootNonce = withdrawalRootToNonce[calculatedRoot];
      
              return (supplier, maxAuthorizedAccountNonce, withdrawalRootNonce);
          }
      
          /**
           * @notice Validates that the parameters are valid for the requested withdrawal
           * @param _partition Source partition of the tokens
           * @param _operator Address that is executing the withdrawal
           * @param _value Number of tokens to be transferred
           * @param _supplier The address whose account is authorized
           * @param _maxAuthorizedAccountNonce The maximum existing used withdrawal nonce for the
           * supplier and partition
           * @param _withdrawalRootNonce The active withdrawal root nonce found based on the supplied
           * data and Merkle proof
           * @return true if the withdrawal data is valid, otherwise false
           */
          function _validateWithdrawalData(
              bytes32 _partition,
              address _operator,
              uint256 _value,
              address _supplier,
              uint256 _maxAuthorizedAccountNonce,
              uint256 _withdrawalRootNonce
          ) internal view returns (bool) {
              return
                  // Only owner, withdrawal publisher or supplier can invoke withdrawals
                  (_operator == owner() || _operator == withdrawalPublisher || _operator == _supplier) &&
                  // Ensure maxAuthorizedAccountNonce has not been exceeded
                  (addressToWithdrawalNonce[_partition][_supplier] <= _maxAuthorizedAccountNonce) &&
                  // Ensure we are within the global withdrawal limit
                  (_value <= withdrawalLimit) &&
                  // Merkle tree proof is valid
                  (_withdrawalRootNonce > 0) &&
                  // Ensure the withdrawal root is more recent than the maxAuthorizedAccountNonce
                  (_withdrawalRootNonce > _maxAuthorizedAccountNonce);
          }
      
          /**********************************************************************************************
           * Fallback Withdrawals
           *********************************************************************************************/
      
          /**
           * @notice Validates fallback withdrawal data
           * @param _partition Source partition of the withdrawal
           * @param _operator Address that is invoking the transfer
           * @param _value Number of tokens to be transferred
           * @param _operatorData Contains the fallback withdrawal authorization data
           * @return true if the fallback withdrawal data is valid, otherwise false
           */
          function _validateFallbackWithdrawal(
              bytes32 _partition,
              address _operator,
              uint256 _value,
              bytes memory _operatorData
          ) internal view returns (bool) {
              (
                  address supplier,
                  uint256 maxCumulativeWithdrawalAmount,
                  uint256 newCumulativeWithdrawalAmount,
                  bytes32 calculatedRoot
              ) = _getFallbackWithdrawalData(_partition, _value, _operatorData);
      
              return
                  _validateFallbackWithdrawalData(
                      _operator,
                      maxCumulativeWithdrawalAmount,
                      newCumulativeWithdrawalAmount,
                      supplier,
                      calculatedRoot
                  );
          }
      
          /**
           * @notice Validates the fallback withdrawal data and updates state to reflect the transfer
           * @param _partition Source partition of the withdrawal
           * @param _operator Address that is invoking the transfer
           * @param _value Number of tokens to be transferred
           * @param _operatorData Contains the fallback withdrawal authorization data
           */
          function _executeFallbackWithdrawal(
              bytes32 _partition,
              address _operator,
              uint256 _value,
              bytes memory _operatorData
          ) internal {
              (
                  address supplier,
                  uint256 maxCumulativeWithdrawalAmount,
                  uint256 newCumulativeWithdrawalAmount,
                  bytes32 calculatedRoot
              ) = _getFallbackWithdrawalData(_partition, _value, _operatorData);
      
              require(
                  _validateFallbackWithdrawalData(
                      _operator,
                      maxCumulativeWithdrawalAmount,
                      newCumulativeWithdrawalAmount,
                      supplier,
                      calculatedRoot
                  ),
                  "Transfer unauthorized"
              );
      
              addressToCumulativeAmountWithdrawn[_partition][supplier] = newCumulativeWithdrawalAmount;
      
              addressToWithdrawalNonce[_partition][supplier] = maxWithdrawalRootNonce;
      
              emit FallbackWithdrawal(supplier, _partition, _value);
          }
      
          /**
           * @notice Extracts withdrawal data from the supplied parameters
           * @param _partition Source partition of the withdrawal
           * @param _value Number of tokens to be transferred
           * @param _operatorData Contains the fallback withdrawal authorization data, including the
           * fallback withdrawal operation flag, supplier, max cumulative withdrawal amount, and Merkle
           * proof.
           * @return supplier, the address whose account is authorized
           * @return maxCumulativeWithdrawalAmount, the maximum amount of tokens that can be withdrawn
           * for the supplier's account, including both withdrawals and fallback withdrawals
           * @return newCumulativeWithdrawalAmount, the new total of all withdrawals include the
           * current request
           * @return calculatedRoot, the Merkle tree root calculated based on the supplied data and proof
           */
          function _getFallbackWithdrawalData(
              bytes32 _partition,
              uint256 _value,
              bytes memory _operatorData
          )
              internal
              view
              returns (
                  address, /* supplier */
                  uint256, /* maxCumulativeWithdrawalAmount */
                  uint256, /* newCumulativeWithdrawalAmount */
                  bytes32 /* calculatedRoot */
              )
          {
              (
                  address supplier,
                  uint256 maxCumulativeWithdrawalAmount,
                  bytes32[] memory merkleProof
              ) = _decodeWithdrawalOperatorData(_operatorData);
      
              uint256 newCumulativeWithdrawalAmount = SafeMath.add(
                  _value,
                  addressToCumulativeAmountWithdrawn[_partition][supplier]
              );
      
              bytes32 leafDataHash = _calculateFallbackLeaf(
                  supplier,
                  _partition,
                  maxCumulativeWithdrawalAmount
              );
              bytes32 calculatedRoot = _calculateMerkleRoot(merkleProof, leafDataHash);
      
              return (
                  supplier,
                  maxCumulativeWithdrawalAmount,
                  newCumulativeWithdrawalAmount,
                  calculatedRoot
              );
          }
      
          /**
           * @notice Validates that the parameters are valid for the requested fallback withdrawal
           * @param _operator Address that is executing the withdrawal
           * @param _maxCumulativeWithdrawalAmount, the maximum amount of tokens that can be withdrawn
           * for the supplier's account, including both withdrawals and fallback withdrawals
           * @param _newCumulativeWithdrawalAmount, the new total of all withdrawals include the
           * current request
           * @param _supplier The address whose account is authorized
           * @param _calculatedRoot The Merkle tree root calculated based on the supplied data and proof
           * @return true if the fallback withdrawal data is valid, otherwise false
           */
          function _validateFallbackWithdrawalData(
              address _operator,
              uint256 _maxCumulativeWithdrawalAmount,
              uint256 _newCumulativeWithdrawalAmount,
              address _supplier,
              bytes32 _calculatedRoot
          ) internal view returns (bool) {
              return
                  // Only owner or supplier can invoke the fallback withdrawal
                  (_operator == owner() || _operator == _supplier) &&
                  // Ensure we have entered fallback mode
                  (SafeMath.add(fallbackSetDate, fallbackWithdrawalDelaySeconds) <= block.timestamp) &&
                  // Check that the maximum allowable withdrawal for the supplier has not been exceeded
                  (_newCumulativeWithdrawalAmount <= _maxCumulativeWithdrawalAmount) &&
                  // Merkle tree proof is valid
                  (fallbackRoot == _calculatedRoot);
          }
      
          /**********************************************************************************************
           * Supply Refunds
           *********************************************************************************************/
      
          /**
           * @notice Validates refund data
           * @param _partition Source partition of the refund
           * @param _operator Address that is invoking the transfer
           * @param _value Number of tokens to be transferred
           * @param _operatorData Contains the refund authorization data
           * @return true if the refund data is valid, otherwise false
           */
          function _validateRefund(
              bytes32 _partition,
              address _operator,
              uint256 _value,
              bytes memory _operatorData
          ) internal view returns (bool) {
              (uint256 _supplyNonce, Supply memory supply) = _getRefundData(_operatorData);
      
              return _verifyRefundData(_partition, _operator, _value, _supplyNonce, supply);
          }
      
          /**
           * @notice Validates the refund data and updates state to reflect the transfer
           * @param _partition Source partition of the refund
           * @param _operator Address that is invoking the transfer
           * @param _value Number of tokens to be transferred
           * @param _operatorData Contains the refund authorization data
           */
          function _executeRefund(
              bytes32 _partition,
              address _operator,
              uint256 _value,
              bytes memory _operatorData
          ) internal {
              (uint256 nonce, Supply memory supply) = _getRefundData(_operatorData);
      
              require(
                  _verifyRefundData(_partition, _operator, _value, nonce, supply),
                  "Transfer unauthorized"
              );
      
              delete nonceToSupply[nonce];
      
              emit SupplyRefund(supply.supplier, _partition, supply.amount, nonce);
          }
      
          /**
           * @notice Extracts refund data from the supplied parameters
           * @param _operatorData Contains the refund authorization data, including the refund
           * operation flag and supply nonce.
           * @return supplyNonce, nonce of the recorded supply
           * @return supply, The supplier, partition and amount of tokens in the original supply
           */
          function _getRefundData(bytes memory _operatorData)
              internal
              view
              returns (uint256, Supply memory)
          {
              uint256 _supplyNonce = _decodeRefundOperatorData(_operatorData);
              Supply memory supply = nonceToSupply[_supplyNonce];
      
              return (_supplyNonce, supply);
          }
      
          /**
           * @notice Validates that the parameters are valid for the requested refund
           * @param _partition Source partition of the tokens
           * @param _operator Address that is executing the refund
           * @param _value Number of tokens to be transferred
           * @param _supplyNonce nonce of the recorded supply
           * @param _supply The supplier, partition and amount of tokens in the original supply
           * @return true if the refund data is valid, otherwise false
           */
          function _verifyRefundData(
              bytes32 _partition,
              address _operator,
              uint256 _value,
              uint256 _supplyNonce,
              Supply memory _supply
          ) internal view returns (bool) {
              return
                  // Supply record exists
                  (_supply.amount > 0) &&
                  // Only owner or supplier can invoke the refund
                  (_operator == owner() || _operator == _supply.supplier) &&
                  // Requested partition matches the Supply record
                  (_partition == _supply.partition) &&
                  // Requested value matches the Supply record
                  (_value == _supply.amount) &&
                  // Ensure we have entered fallback mode
                  (SafeMath.add(fallbackSetDate, fallbackWithdrawalDelaySeconds) <= block.timestamp) &&
                  // Supply has not already been included in the fallback withdrawal data
                  (_supplyNonce > fallbackMaxIncludedSupplyNonce);
          }
      
          /**********************************************************************************************
           * Direct Transfers
           *********************************************************************************************/
      
          /**
           * @notice Validates direct transfer data
           * @param _operator Address that is invoking the transfer
           * @param _value Number of tokens to be transferred
           * @return true if the direct transfer data is valid, otherwise false
           */
          function _validateDirectTransfer(address _operator, uint256 _value)
              internal
              view
              returns (bool)
          {
              return
                  // Only owner and directTransferer can invoke withdrawals
                  (_operator == owner() || _operator == directTransferer) &&
                  // Ensure we are within the global withdrawal limit
                  (_value <= withdrawalLimit);
          }
      
          /**
           * @notice Validates the direct transfer data and updates state to reflect the transfer
           * @param _partition Source partition of the direct transfer
           * @param _operator Address that is invoking the transfer
           * @param _to The target address of the tokens.
           * @param _value Number of tokens to be transferred
           * @param _data Data attached to the transfer. Typically includes partition change information.
           */
          function _executeDirectTransfer(
              bytes32 _partition,
              address _operator,
              address _to,
              uint256 _value,
              bytes memory _data
          ) internal {
              require(_validateDirectTransfer(_operator, _value), "Transfer unauthorized");
      
              withdrawalLimit = SafeMath.sub(withdrawalLimit, _value);
      
              bytes32 to_partition = PartitionUtils._getDestinationPartition(_data, _partition);
      
              emit DirectTransfer(_operator, _partition, _to, to_partition, _value);
          }
      
          /**********************************************************************************************
           * Release Request
           *********************************************************************************************/
      
          /**
           * @notice Emits a release request event that can be used to trigger the release of tokens
           * @param _partition Parition from which the tokens should be released
           * @param _amount Number of tokens requested to be released
           * @param _data Metadata to include with the release request
           */
          function requestRelease(
              bytes32 _partition,
              uint256 _amount,
              bytes memory _data
          ) external {
              emit ReleaseRequest(msg.sender, _partition, _amount, _data);
          }
      
          /**********************************************************************************************
           * Partition Management
           *********************************************************************************************/
      
          /**
           * @notice Adds a partition to the set allowed to receive tokens
           * @param _partition Parition to be permitted for incoming transfers
           */
          function addPartition(bytes32 _partition) external {
              require(msg.sender == owner() || msg.sender == partitionManager, "Invalid sender");
              require(partitions[_partition] == false, "Partition already permitted");
      
              (bytes4 prefix, , address partitionOwner) = PartitionUtils._splitPartition(_partition);
      
              require(prefix == PARTITION_PREFIX, "Invalid partition prefix");
              require(partitionOwner == address(this), "Invalid partition owner");
      
              partitions[_partition] = true;
      
              emit PartitionAdded(_partition);
          }
      
          /**
           * @notice Removes a partition from the set allowed to receive tokens
           * @param _partition Parition to be disallowed from incoming transfers
           */
          function removePartition(bytes32 _partition) external {
              require(msg.sender == owner() || msg.sender == partitionManager, "Invalid sender");
              require(partitions[_partition], "Partition not permitted");
      
              delete partitions[_partition];
      
              emit PartitionRemoved(_partition);
          }
      
          /**********************************************************************************************
           * Withdrawal Management
           *********************************************************************************************/
      
          /**
           * @notice Modifies the withdrawal limit by the provided amount.
           * @param _amount Limit delta
           */
          function modifyWithdrawalLimit(int256 _amount) external {
              require(msg.sender == owner() || msg.sender == withdrawalLimitPublisher, "Invalid sender");
              uint256 oldLimit = withdrawalLimit;
              if (_amount < 0) {
                  uint256 unsignedAmount = uint256(-_amount);
                  withdrawalLimit = SafeMath.sub(withdrawalLimit, unsignedAmount);
              } else {
                  uint256 unsignedAmount = uint256(_amount);
                  withdrawalLimit = SafeMath.add(withdrawalLimit, unsignedAmount);
              }
              emit WithdrawalLimitUpdate(oldLimit, withdrawalLimit);
          }
      
          /**
           * @notice Adds the root hash of a Merkle tree containing authorized token withdrawals to the
           * active set
           * @param _root The root hash to be added to the active set
           * @param _nonce The nonce of the new root hash. Must be exactly one higher than the existing
           * max nonce.
           * @param _replacedRoots The root hashes to be removed from the repository.
           */
          function addWithdrawalRoot(
              bytes32 _root,
              uint256 _nonce,
              bytes32[] calldata _replacedRoots
          ) external {
              require(msg.sender == owner() || msg.sender == withdrawalPublisher, "Invalid sender");
      
              require(_root != 0, "Invalid root");
              require(maxWithdrawalRootNonce + 1 == _nonce, "Nonce not current max plus one");
              require(withdrawalRootToNonce[_root] == 0, "Nonce already used");
      
              withdrawalRootToNonce[_root] = _nonce;
              maxWithdrawalRootNonce = _nonce;
      
              emit WithdrawalRootHashAddition(_root, _nonce);
      
              for (uint256 i = 0; i < _replacedRoots.length; i++) {
                  deleteWithdrawalRoot(_replacedRoots[i]);
              }
          }
      
          /**
           * @notice Removes withdrawal root hashes from active set
           * @param _roots The root hashes to be removed from the active set
           */
          function removeWithdrawalRoots(bytes32[] calldata _roots) external {
              require(msg.sender == owner() || msg.sender == withdrawalPublisher, "Invalid sender");
      
              for (uint256 i = 0; i < _roots.length; i++) {
                  deleteWithdrawalRoot(_roots[i]);
              }
          }
      
          /**
           * @notice Removes a withdrawal root hash from active set
           * @param _root The root hash to be removed from the active set
           */
          function deleteWithdrawalRoot(bytes32 _root) private {
              uint256 nonce = withdrawalRootToNonce[_root];
      
              require(nonce > 0, "Root not found");
      
              delete withdrawalRootToNonce[_root];
      
              emit WithdrawalRootHashRemoval(_root, nonce);
          }
      
          /**********************************************************************************************
           * Fallback Management
           *********************************************************************************************/
      
          /**
           * @notice Sets the root hash of the Merkle tree containing fallback
           * withdrawal authorizations.
           * @param _root The root hash of a Merkle tree containing the fallback withdrawal
           * authorizations
           * @param _maxSupplyNonce The nonce of the latest supply whose value is reflected in the
           * fallback withdrawal authorizations.
           */
          function setFallbackRoot(bytes32 _root, uint256 _maxSupplyNonce) external {
              require(msg.sender == owner() || msg.sender == fallbackPublisher, "Invalid sender");
              require(_root != 0, "Invalid root");
              require(
                  SafeMath.add(fallbackSetDate, fallbackWithdrawalDelaySeconds) > block.timestamp,
                  "Fallback is active"
              );
              require(
                  _maxSupplyNonce >= fallbackMaxIncludedSupplyNonce,
                  "Included supply nonce decreased"
              );
              require(_maxSupplyNonce <= supplyNonce, "Included supply nonce exceeds latest supply");
      
              fallbackRoot = _root;
              fallbackMaxIncludedSupplyNonce = _maxSupplyNonce;
              fallbackSetDate = block.timestamp;
      
              emit FallbackRootHashSet(_root, fallbackMaxIncludedSupplyNonce, block.timestamp);
          }
      
          /**
           * @notice Resets the fallback set date to the current block's timestamp. This can be used to
           * delay the start of the fallback period without publishing a new root, or to deactivate the
           * fallback mechanism so a new fallback root may be published.
           */
          function resetFallbackMechanismDate() external {
              require(msg.sender == owner() || msg.sender == fallbackPublisher, "Invalid sender");
              fallbackSetDate = block.timestamp;
      
              emit FallbackMechanismDateReset(fallbackSetDate);
          }
      
          /**
           * @notice Updates the time-lock period before the fallback mechanism is activated after the
           * last fallback root was published.
           * @param _newFallbackDelaySeconds The new delay period in seconds
           */
          function setFallbackWithdrawalDelay(uint256 _newFallbackDelaySeconds) external {
              require(msg.sender == owner(), "Invalid sender");
              require(_newFallbackDelaySeconds != 0, "Invalid zero delay seconds");
              require(_newFallbackDelaySeconds < 10 * 365 days, "Invalid delay over 10 years");
      
              uint256 oldDelay = fallbackWithdrawalDelaySeconds;
              fallbackWithdrawalDelaySeconds = _newFallbackDelaySeconds;
      
              emit FallbackWithdrawalDelayUpdate(oldDelay, _newFallbackDelaySeconds);
          }
      
          /**********************************************************************************************
           * Role Management
           *********************************************************************************************/
      
          /**
           * @notice Updates the Withdrawal Publisher address, the only address other than the owner that
           * can publish / remove withdrawal Merkle tree roots.
           * @param _newWithdrawalPublisher The address of the new Withdrawal Publisher
           * @dev Error invalid sender.
           */
          function setWithdrawalPublisher(address _newWithdrawalPublisher) external {
              require(msg.sender == owner(), "Invalid sender");
      
              address oldValue = withdrawalPublisher;
              withdrawalPublisher = _newWithdrawalPublisher;
      
              emit WithdrawalPublisherUpdate(oldValue, withdrawalPublisher);
          }
      
          /**
           * @notice Updates the Fallback Publisher address, the only address other than the owner that
           * can publish / remove fallback withdrawal Merkle tree roots.
           * @param _newFallbackPublisher The address of the new Fallback Publisher
           * @dev Error invalid sender.
           */
          function setFallbackPublisher(address _newFallbackPublisher) external {
              require(msg.sender == owner(), "Invalid sender");
      
              address oldValue = fallbackPublisher;
              fallbackPublisher = _newFallbackPublisher;
      
              emit FallbackPublisherUpdate(oldValue, fallbackPublisher);
          }
      
          /**
           * @notice Updates the Withdrawal Limit Publisher address, the only address other than the
           * owner that can set the withdrawal limit.
           * @param _newWithdrawalLimitPublisher The address of the new Withdrawal Limit Publisher
           * @dev Error invalid sender.
           */
          function setWithdrawalLimitPublisher(address _newWithdrawalLimitPublisher) external {
              require(msg.sender == owner(), "Invalid sender");
      
              address oldValue = withdrawalLimitPublisher;
              withdrawalLimitPublisher = _newWithdrawalLimitPublisher;
      
              emit WithdrawalLimitPublisherUpdate(oldValue, withdrawalLimitPublisher);
          }
      
          /**
           * @notice Updates the DirectTransferer address, the only address other than the owner that
           * can execute direct transfers
           * @param _newDirectTransferer The address of the new DirectTransferer
           */
          function setDirectTransferer(address _newDirectTransferer) external {
              require(msg.sender == owner(), "Invalid sender");
      
              address oldValue = directTransferer;
              directTransferer = _newDirectTransferer;
      
              emit DirectTransfererUpdate(oldValue, directTransferer);
          }
      
          /**
           * @notice Updates the Partition Manager address, the only address other than the owner that
           * can add and remove permitted partitions
           * @param _newPartitionManager The address of the new PartitionManager
           */
          function setPartitionManager(address _newPartitionManager) external {
              require(msg.sender == owner(), "Invalid sender");
      
              address oldValue = partitionManager;
              partitionManager = _newPartitionManager;
      
              emit PartitionManagerUpdate(oldValue, partitionManager);
          }
      
          /**********************************************************************************************
           * Operator Data Decoders
           *********************************************************************************************/
      
          /**
           * @notice Extract flag from operatorData
           * @param _operatorData The operator data to be decoded
           * @return flag, the transfer operation type
           */
          function _decodeOperatorDataFlag(bytes memory _operatorData) internal pure returns (bytes32) {
              return abi.decode(_operatorData, (bytes32));
          }
      
          /**
           * @notice Extracts the supplier, max authorized nonce, and Merkle proof from the operator data
           * @param _operatorData The operator data to be decoded
           * @return supplier, the address whose account is authorized
           * @return For withdrawals: max authorized nonce, the last used withdrawal root nonce for the
           * supplier and partition. For fallback withdrawals: max cumulative withdrawal amount, the
           * maximum amount of tokens that can be withdrawn for the supplier's account, including both
           * withdrawals and fallback withdrawals
           * @return proof, the Merkle proof to be used for the authorization
           */
          function _decodeWithdrawalOperatorData(bytes memory _operatorData)
              internal
              pure
              returns (
                  address,
                  uint256,
                  bytes32[] memory
              )
          {
              (, address supplier, uint256 nonce, bytes32[] memory proof) = abi.decode(
                  _operatorData,
                  (bytes32, address, uint256, bytes32[])
              );
      
              return (supplier, nonce, proof);
          }
      
          /**
           * @notice Extracts the supply nonce from the operator data
           * @param _operatorData The operator data to be decoded
           * @return nonce, the nonce of the supply to be refunded
           */
          function _decodeRefundOperatorData(bytes memory _operatorData) internal pure returns (uint256) {
              (, uint256 nonce) = abi.decode(_operatorData, (bytes32, uint256));
      
              return nonce;
          }
      
          /**********************************************************************************************
           * Merkle Tree Verification
           *********************************************************************************************/
      
          /**
           * @notice Hashes the supplied data and returns the hash to be used in conjunction with a proof
           * to calculate the Merkle tree root
           * @param _supplier The address whose account is authorized
           * @param _partition Source partition of the tokens
           * @param _value Number of tokens to be transferred
           * @param _maxAuthorizedAccountNonce The maximum existing used withdrawal nonce for the
           * supplier and partition
           * @return leaf, the hash of the supplied data
           */
          function _calculateWithdrawalLeaf(
              address _supplier,
              bytes32 _partition,
              uint256 _value,
              uint256 _maxAuthorizedAccountNonce
          ) internal pure returns (bytes32) {
              return
                  keccak256(abi.encodePacked(_supplier, _partition, _value, _maxAuthorizedAccountNonce));
          }
      
          /**
           * @notice Hashes the supplied data and returns the hash to be used in conjunction with a proof
           * to calculate the Merkle tree root
           * @param _supplier The address whose account is authorized
           * @param _partition Source partition of the tokens
           * @param _maxCumulativeWithdrawalAmount, the maximum amount of tokens that can be withdrawn
           * for the supplier's account, including both withdrawals and fallback withdrawals
           * @return leaf, the hash of the supplied data
           */
          function _calculateFallbackLeaf(
              address _supplier,
              bytes32 _partition,
              uint256 _maxCumulativeWithdrawalAmount
          ) internal pure returns (bytes32) {
              return keccak256(abi.encodePacked(_supplier, _partition, _maxCumulativeWithdrawalAmount));
          }
      
          /**
           * @notice Calculates the Merkle root for the unique Merkle tree described by the provided
             Merkle proof and leaf hash.
           * @param _merkleProof The sibling node hashes at each level of the tree.
           * @param _leafHash The hash of the leaf data for which merkleProof is an inclusion proof.
           * @return The calculated Merkle root.
           */
          function _calculateMerkleRoot(bytes32[] memory _merkleProof, bytes32 _leafHash)
              private
              pure
              returns (bytes32)
          {
              bytes32 computedHash = _leafHash;
      
              for (uint256 i = 0; i < _merkleProof.length; i++) {
                  bytes32 proofElement = _merkleProof[i];
      
                  if (computedHash < proofElement) {
                      computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
                  } else {
                      computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
                  }
              }
      
              return computedHash;
          }
      }

      File 3 of 4: 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
              }
          }
      }

      File 4 of 4: CollateralPoolPartitionValidator
      // SPDX-License-Identifier: MIT
      
      pragma solidity 0.6.10;
      
      abstract contract ERC1820Registry {
          function setInterfaceImplementer(
              address _addr,
              bytes32 _interfaceHash,
              address _implementer
          ) external virtual;
      
          function getInterfaceImplementer(address _addr, bytes32 _interfaceHash)
              external
              virtual
              view
              returns (address);
      
          function setManager(address _addr, address _newManager) external virtual;
      
          function getManager(address _addr) public virtual view returns (address);
      }
      
      /// Base client to interact with the registry.
      contract ERC1820Client {
          ERC1820Registry constant ERC1820REGISTRY = ERC1820Registry(
              0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
          );
      
          function setInterfaceImplementation(
              string memory _interfaceLabel,
              address _implementation
          ) internal {
              bytes32 interfaceHash = keccak256(abi.encodePacked(_interfaceLabel));
              ERC1820REGISTRY.setInterfaceImplementer(
                  address(this),
                  interfaceHash,
                  _implementation
              );
          }
      
          function interfaceAddr(address addr, string memory _interfaceLabel)
              internal
              view
              returns (address)
          {
              bytes32 interfaceHash = keccak256(abi.encodePacked(_interfaceLabel));
              return ERC1820REGISTRY.getInterfaceImplementer(addr, interfaceHash);
          }
      
          function delegateManagement(address _newManager) internal {
              ERC1820REGISTRY.setManager(address(this), _newManager);
          }
      }
      
      contract ERC1820Implementer {
          /**
           * @dev ERC1820 well defined magic value indicating the contract has
           * registered with the ERC1820Registry that it can implement an interface.
           */
          bytes32 constant ERC1820_ACCEPT_MAGIC = keccak256(
              abi.encodePacked("ERC1820_ACCEPT_MAGIC")
          );
      
          /**
           * @dev Mapping of interface name keccak256 hashes for which this contract
           * implements the interface.
           * @dev Only settable internally.
           */
          mapping(bytes32 => bool) internal _interfaceHashes;
      
          /**
           * @notice Indicates whether the contract implements the interface `_interfaceHash`
           * for the address `_addr`.
           * @param _interfaceHash keccak256 hash of the name of the interface.
           * @return ERC1820_ACCEPT_MAGIC only if the contract implements `ìnterfaceHash`
           * for the address `_addr`.
           * @dev In this implementation, the `_addr` (the address for which the
           * contract will implement the interface) is always `address(this)`.
           */
          function canImplementInterfaceForAddress(
              bytes32 _interfaceHash,
              address // Comments to avoid compilation warnings for unused variables. /*addr*/
          ) external view returns (bytes32) {
              if (_interfaceHashes[_interfaceHash]) {
                  return ERC1820_ACCEPT_MAGIC;
              } else {
                  return "";
              }
          }
      
          /**
           * @notice Internally set the fact this contract implements the interface
           * identified by `_interfaceLabel`
           * @param _interfaceLabel String representation of the interface.
           */
          function _setInterface(string memory _interfaceLabel) internal {
              _interfaceHashes[keccak256(abi.encodePacked(_interfaceLabel))] = true;
          }
      }
      
      /**
       * @notice Partition strategy validator hooks for Amp
       */
      interface IAmpPartitionStrategyValidator {
          function tokensFromPartitionToValidate(
              bytes4 _functionSig,
              bytes32 _partition,
              address _operator,
              address _from,
              address _to,
              uint256 _value,
              bytes calldata _data,
              bytes calldata _operatorData
          ) external;
      
          function tokensToPartitionToValidate(
              bytes4 _functionSig,
              bytes32 _partition,
              address _operator,
              address _from,
              address _to,
              uint256 _value,
              bytes calldata _data,
              bytes calldata _operatorData
          ) external;
      
          function isOperatorForPartitionScope(
              bytes32 _partition,
              address _operator,
              address _tokenHolder
          ) external view returns (bool);
      }
      
      /**
       * @title PartitionUtils
       * @notice Partition related helper functions.
       */
      
      library PartitionUtils {
          bytes32 public constant CHANGE_PARTITION_FLAG = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
      
          /**
           * @notice Retrieve the destination partition from the 'data' field.
           * A partition change is requested ONLY when 'data' starts with the flag:
           *
           *   0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
           *
           * When the flag is detected, the destination partition is extracted from the
           * 32 bytes following the flag.
           * @param _data Information attached to the transfer. Will contain the
           * destination partition if a change is requested.
           * @param _fallbackPartition Partition value to return if a partition change
           * is not requested in the `_data`.
           * @return toPartition Destination partition. If the `_data` does not contain
           * the prefix and bytes32 partition in the first 64 bytes, the method will
           * return the provided `_fromPartition`.
           */
          function _getDestinationPartition(bytes memory _data, bytes32 _fallbackPartition)
              internal
              pure
              returns (bytes32)
          {
              if (_data.length < 64) {
                  return _fallbackPartition;
              }
      
              (bytes32 flag, bytes32 toPartition) = abi.decode(_data, (bytes32, bytes32));
              if (flag == CHANGE_PARTITION_FLAG) {
                  return toPartition;
              }
      
              return _fallbackPartition;
          }
      
          /**
           * @notice Helper to get the strategy identifying prefix from the `_partition`.
           * @param _partition Partition to get the prefix for.
           * @return 4 byte partition strategy prefix.
           */
          function _getPartitionPrefix(bytes32 _partition) internal pure returns (bytes4) {
              return bytes4(_partition);
          }
      
          /**
           * @notice Helper method to split the partition into the prefix, sub partition
           * and partition owner components.
           * @param _partition The partition to split into parts.
           * @return The 4 byte partition prefix, 8 byte sub partition, and final 20
           * bytes representing an address.
           */
          function _splitPartition(bytes32 _partition)
              internal
              pure
              returns (
                  bytes4,
                  bytes8,
                  address
              )
          {
              bytes4 prefix = bytes4(_partition);
              bytes8 subPartition = bytes8(_partition << 32);
              address addressPart = address(uint160(uint256(_partition)));
              return (prefix, subPartition, addressPart);
          }
      
          /**
           * @notice Helper method to get a partition strategy ERC1820 interface name
           * based on partition prefix.
           * @param _prefix 4 byte partition prefix.
           * @dev Each 4 byte prefix has a unique interface name so that an individual
           * hook implementation can be set for each prefix.
           */
          function _getPartitionStrategyValidatorIName(bytes4 _prefix)
              internal
              pure
              returns (string memory)
          {
              return string(abi.encodePacked("AmpPartitionStrategyValidator", _prefix));
          }
      }
      
      /**
       * @title Base contract that satisfies the IAmpPartitionStrategyValidator
       * interface
       */
      contract AmpPartitionStrategyValidatorBase is
          IAmpPartitionStrategyValidator,
          ERC1820Client,
          ERC1820Implementer
      {
          /**
           * @notice Partition prefix the hooks are valid for.
           * @dev Must to be set by the parent contract.
           */
          bytes4 public partitionPrefix;
      
          /**
           * @notice Amp contract address.
           */
          address public amp;
      
          /**
           * @notice Initialize the partition prefix and register the implementation
           * with the ERC1820 registry for the dynamic interface name.
           * @param _prefix Partition prefix the hooks are valid for.
           * @param _amp The address of the Amp contract.
           */
          constructor(bytes4 _prefix, address _amp) public {
              partitionPrefix = _prefix;
      
              string memory iname = PartitionUtils._getPartitionStrategyValidatorIName(
                  partitionPrefix
              );
              ERC1820Implementer._setInterface(iname);
      
              amp = _amp;
          }
      
          /**
           * @dev Placeholder to satisfy IAmpPartitionSpaceValidator interface that
           * can be overridden by parent.
           */
          function tokensFromPartitionToValidate(
              bytes4, /* functionSig */
              bytes32, /* fromPartition */
              address, /* operator */
              address, /* from */
              address, /* to */
              uint256, /* value */
              bytes calldata, /* data */
              bytes calldata /* operatorData */
          ) external virtual override {}
      
          /**
           * @dev Placeholder to satisfy IAmpPartitionSpaceValidator interface that
           * can be overridden by parent.
           */
          function tokensToPartitionToValidate(
              bytes4, /* functionSig */
              bytes32, /* fromPartition */
              address, /* operator */
              address, /* from */
              address, /* to */
              uint256, /* value */
              bytes calldata, /* data */
              bytes calldata /* operatorData */
          ) external virtual override {}
      
          /**
           * @notice Report if address is an operator for a partition based on the
           * partition's strategy.
           * @dev Placeholder that can be overridden by parent.
           */
          function isOperatorForPartitionScope(
              bytes32, /* partition */
              address, /* operator */
              address /* tokenHolder */
          ) external virtual override view returns (bool) {
              return false;
          }
      }
      
      
      interface IAmp {
          function isCollateralManager(address) external view returns (bool);
      }
      
      /**
       * @title CollateralPoolPartitionValidator
       */
      contract CollateralPoolPartitionValidator is AmpPartitionStrategyValidatorBase {
          bytes4 constant PARTITION_PREFIX = 0xCCCCCCCC;
      
          constructor(address _amp)
              public
              AmpPartitionStrategyValidatorBase(PARTITION_PREFIX, _amp)
          {}
      
          /**
           * @notice Reports if the token holder is an operator for the partition.
           * @dev The `_operator` address param is unused. For this strategy, this will
           * be being called on behalf of suppliers, as they have sent their tokens
           * to the collateral manager address, and are now trying to execute a
           * transfer from the pool. This implies that the pool sender hook
           * MUST be implemented in such a way as to restrict any unauthorized
           * transfers, as the partitions affected by this strategy will allow
           * all callers to make an attempt to transfer from the collateral
           * managers partition.
           * @param _partition The partition to check.
           * @param _tokenHolder The collateral manager holding the pool of tokens.
           * @return The operator check for this strategy returns true if the partition
           * owner (identified by the final 20 bytes of the partition) is the
           * same as the token holder address, as in this case the token holder
           * is the collateral manager address.
           */
          function isOperatorForPartitionScope(
              bytes32 _partition,
              address, /* operator */
              address _tokenHolder
          ) external override view returns (bool) {
              require(msg.sender == address(amp), "Hook must be called by amp");
      
              (, , address partitionOwner) = PartitionUtils._splitPartition(_partition);
              if (!IAmp(amp).isCollateralManager(partitionOwner)) {
                  return false;
              }
      
              return _tokenHolder == partitionOwner;
          }
      
          /**
           * @notice Validate the rules of the strategy when tokens are being sent to
           * a partition under the purview of the strategy.
           * @dev The `_toPartition` must be formatted with the PARTITION_PREFIX as the
           * first 4 bytes, the `_to` value as the final 20 bytes. The 8 bytes in the
           * middle can be used by the manager to create sub partitions within their
           * impelemntation.
           * @param _toPartition The partition the tokens are transferred to.
           * @param _to The address of the collateral manager.
           */
          function tokensToPartitionToValidate(
              bytes4, /* functionSig */
              bytes32 _toPartition,
              address, /* operator */
              address, /* from */
              address _to,
              uint256, /* value */
              bytes calldata, /* _data */
              bytes calldata /* operatorData */
          ) external override {
              require(msg.sender == address(amp), "Hook must be called by amp");
      
              (, , address toPartitionOwner) = PartitionUtils._splitPartition(_toPartition);
      
              require(
                  _to == toPartitionOwner,
                  "Transfers to this partition must be to the partitionOwner"
              );
              require(
                  IAmp(amp).isCollateralManager(toPartitionOwner),
                  "Partition owner is not a registered collateral manager"
              );
          }
      }