ETH Price: $2,551.91 (-3.39%)

Transaction Decoder

Block:
16656663 at Feb-18-2023 04:19:59 PM +UTC
Transaction Fee:
0.003472855264650486 ETH $8.86
Gas Used:
103,849 Gas / 33.441393414 Gwei

Emitted Events:

389 UniswapV2Pair.Transfer( from=TokenPool, to=[Sender] 0x22acb809d08ae7c79f0b84513efce0577acfe514, value=193570857170603941796 )
390 GebLenderFirstResortRewardsVested.Exit( account=[Sender] 0x22acb809d08ae7c79f0b84513efce0577acfe514, price=193570857170603941796, amount=193570857170603941796 )

Account State Difference:

  Address   Before After State Difference Code
0x22Acb809...77acFE514
4.178457801202419911 Eth
Nonce: 1160
4.174984945937769425 Eth
Nonce: 1161
0.003472855264650486
0x69c6C08B...b04EFc106
(beaverbuild)
105.839070286198649219 Eth105.839174135198649219 Eth0.000103849
0xd6F3768E...907cEceF9

Execution Trace

GebLenderFirstResortRewardsVested.CALL( )
  • AccountingEngine.STATICCALL( )
    • SAFEEngine.debtBalance( 0xcEe6Aa1aB47d0Fb0f24f51A3072EC16E20F90fcE ) => ( 0 )
    • AccountingEngine.STATICCALL( )
    • SAFEEngine.coinBalance( 0xcEe6Aa1aB47d0Fb0f24f51A3072EC16E20F90fcE ) => ( 519480858432246869080309868835994694059473156436832 )
    • TokenPool.STATICCALL( )
      • UniswapV2Pair.balanceOf( 0x82DAf3b6FCd7d4B27cd9c6DBB6BE93a7B38570AE ) => ( 5584447722235049144662 )
      • TokenPool.transfer( to=0x22Acb809d08aE7c79F0b84513EfCE0577acFE514, wad=193570857170603941796 )
        • UniswapV2Pair.transfer( to=0x22Acb809d08aE7c79F0b84513EfCE0577acFE514, value=193570857170603941796 ) => ( True )
          File 1 of 5: GebLenderFirstResortRewardsVested
          /// GebLenderFirstResortRewardsVested.sol
          
          // Copyright (C) 2021 Reflexer Labs, INC
          //
          // This program is free software: you can redistribute it and/or modify
          // it under the terms of the GNU Affero General Public License as published by
          // the Free Software Foundation, either version 3 of the License, or
          // (at your option) any later version.
          //
          // This program is distributed in the hope that it will be useful,
          // but WITHOUT ANY WARRANTY; without even the implied warranty of
          // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
          // GNU Affero General Public License for more details.
          //
          // You should have received a copy of the GNU Affero General Public License
          // along with this program.  If not, see <https://www.gnu.org/licenses/>.
          
          pragma solidity 0.6.7;
          
          /**
           * @dev Contract module that helps prevent reentrant calls to a function.
           *
           * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
           * available, which can be applied to functions to make sure there are no nested
           * (reentrant) calls to them.
           *
           * Note that because there is a single `nonReentrant` guard, functions marked as
           * `nonReentrant` may not call one another. This can be worked around by making
           * those functions `private`, and then adding `external` `nonReentrant` entry
           * points to them.
           *
           * TIP: If you would like to learn more about reentrancy and alternative ways
           * to protect against it, check out our blog post
           * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
           */
          abstract contract ReentrancyGuard {
              // Booleans are more expensive than uint256 or any type that takes up a full
              // word because each write operation emits an extra SLOAD to first read the
              // slot's contents, replace the bits taken up by the boolean, and then write
              // back. This is the compiler's defense against contract upgrades and
              // pointer aliasing, and it cannot be disabled.
          
              // The values being non-zero value makes deployment a bit more expensive,
              // but in exchange the refund on every call to nonReentrant will be lower in
              // amount. Since refunds are capped to a percentage of the total
              // transaction's gas, it is best to keep them low in cases like this one, to
              // increase the likelihood of the full refund coming into effect.
              uint256 private constant _NOT_ENTERED = 1;
              uint256 private constant _ENTERED = 2;
          
              uint256 private _status;
          
              constructor () internal {
                  _status = _NOT_ENTERED;
              }
          
              /**
               * @dev Prevents a contract from calling itself, directly or indirectly.
               * Calling a `nonReentrant` function from another `nonReentrant`
               * function is not supported. It is possible to prevent this from happening
               * by making the `nonReentrant` function external, and make it call a
               * `private` function that does the actual work.
               */
              modifier nonReentrant() {
                  // On the first call to nonReentrant, _notEntered will be true
                  require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
          
                  // Any calls to nonReentrant after this point will fail
                  _status = _ENTERED;
          
                  _;
          
                  // By storing the original value once again, a refund is triggered (see
                  // https://eips.ethereum.org/EIPS/eip-2200)
                  _status = _NOT_ENTERED;
              }
          }
          
          abstract contract TokenLike {
              function decimals() virtual public view returns (uint8);
              function totalSupply() virtual public view returns (uint256);
              function balanceOf(address) virtual public view returns (uint256);
              function mint(address, uint) virtual public;
              function burn(address, uint) virtual public;
              function approve(address, uint256) virtual external returns (bool);
              function transfer(address, uint256) virtual external returns (bool);
              function transferFrom(address,address,uint256) virtual external returns (bool);
          }
          abstract contract AuctionHouseLike {
              function activeStakedTokenAuctions() virtual public view returns (uint256);
              function startAuction(uint256, uint256) virtual external returns (uint256);
          }
          abstract contract AccountingEngineLike {
              function debtAuctionBidSize() virtual public view returns (uint256);
              function unqueuedUnauctionedDebt() virtual public view returns (uint256);
          }
          abstract contract SAFEEngineLike {
              function coinBalance(address) virtual public view returns (uint256);
              function debtBalance(address) virtual public view returns (uint256);
          }
          abstract contract RewardDripperLike {
              function dripReward() virtual external;
              function dripReward(address) virtual external;
              function rewardPerBlock() virtual external view returns (uint256);
              function rewardToken() virtual external view returns (TokenLike);
          }
          abstract contract StakingRewardsEscrowLike {
              function escrowRewards(address, uint256) virtual external;
          }
          
          // Stores tokens, owned by GebLenderFirstResortRewardsVested
          contract TokenPool {
              TokenLike public token;
              address   public owner;
          
              constructor(address token_) public {
                  token = TokenLike(token_);
                  owner = msg.sender;
              }
          
              // @notice Transfers tokens from the pool (callable by owner only)
              function transfer(address to, uint256 wad) public {
                  require(msg.sender == owner, "unauthorized");
                  require(token.transfer(to, wad), "TokenPool/failed-transfer");
              }
          
              // @notice Returns token balance of the pool
              function balance() public view returns (uint256) {
                  return token.balanceOf(address(this));
              }
          }
          
          contract GebLenderFirstResortRewardsVested is ReentrancyGuard {
              // --- Auth ---
              mapping (address => uint) public authorizedAccounts;
              /**
               * @notice Add auth to an account
               * @param account Account to add auth to
               */
              function addAuthorization(address account) virtual external isAuthorized {
                  authorizedAccounts[account] = 1;
                  emit AddAuthorization(account);
              }
              /**
               * @notice Remove auth from an account
               * @param account Account to remove auth from
               */
              function removeAuthorization(address account) virtual external isAuthorized {
                  authorizedAccounts[account] = 0;
                  emit RemoveAuthorization(account);
              }
              /**
              * @notice Checks whether msg.sender can call an authed function
              **/
              modifier isAuthorized {
                  require(authorizedAccounts[msg.sender] == 1, "GebLenderFirstResortRewardsVested/account-not-authorized");
                  _;
              }
          
              // --- Structs ---
              struct ExitRequest {
                  // Exit window deadline
                  uint256 deadline;
                  // Ancestor amount queued for exit
                  uint256 lockedAmount;
              }
          
              // --- Variables ---
              // Flag that allows/blocks joining
              bool      public canJoin;
              // Flag that indicates whether canPrintProtocolTokens can ignore auctioning ancestor tokens
              bool      public bypassAuctions;
              // Whether the contract allows forced exits or not
              bool      public forcedExit;
              // Last block when a reward was pulled
              uint256   public lastRewardBlock;
              // The current delay enforced on an exit
              uint256   public exitDelay;
              // Min maount of ancestor tokens that must remain in the contract and not be auctioned
              uint256   public minStakedTokensToKeep;
              // Max number of auctions that can be active at a time
              uint256   public maxConcurrentAuctions;
              // Amount of ancestor tokens to auction at a time
              uint256   public tokensToAuction;
              // Initial amount of system coins to request in exchange for tokensToAuction
              uint256   public systemCoinsToRequest;
              // Amount of rewards per share accumulated (total, see rewardDebt for more info)
              uint256   public accTokensPerShare;
              // Balance of the rewards token in this contract since last update
              uint256   public rewardsBalance;
              // Staked Supply (== sum of all staked balances)
              uint256   public stakedSupply;
              // Percentage of claimed rewards that will be vested
              uint256   public percentageVested;
              // Whether the escrow is paused or not
              uint256   public escrowPaused;
          
              // Balances (not affected by slashing)
              mapping(address => uint256)    public descendantBalanceOf;
              // Exit data
              mapping(address => ExitRequest) public exitRequests;
              // The amount of tokens inneligible for claiming rewards (see formula below)
              mapping(address => uint256)    internal rewardDebt;
              // Pending reward = (descendant.balanceOf(user) * accTokensPerShare) - rewardDebt[user]
          
              // The token being deposited in the pool
              TokenPool                public ancestorPool;
              // The token used to pay rewards
              TokenPool                public rewardPool;
              // Descendant token
              TokenLike                public descendant;
              // Auction house for staked tokens
              AuctionHouseLike         public auctionHouse;
              // Accounting engine contract
              AccountingEngineLike     public accountingEngine;
              // The safe engine contract
              SAFEEngineLike           public safeEngine;
              // Contract that drips rewards
              RewardDripperLike        public rewardDripper;
              // Escrow for rewards
              StakingRewardsEscrowLike public escrow;
          
              // Max delay that can be enforced for an exit
              uint256 public immutable MAX_DELAY;
          
              // --- Events ---
              event AddAuthorization(address account);
              event RemoveAuthorization(address account);
              event ModifyParameters(bytes32 indexed parameter, uint256 data);
              event ModifyParameters(bytes32 indexed parameter, address data);
              event ToggleJoin(bool canJoin);
              event ToggleBypassAuctions(bool bypassAuctions);
              event ToggleForcedExit(bool forcedExit);
              event AuctionAncestorTokens(address auctionHouse, uint256 amountAuctioned, uint256 amountRequested);
              event RequestExit(address indexed account, uint256 deadline, uint256 amount);
              event Join(address indexed account, uint256 price, uint256 amount);
              event Exit(address indexed account, uint256 price, uint256 amount);
              event RewardsPaid(address account, uint256 amount);
              event EscrowRewards(address escrow, address who, uint256 amount);
              event PoolUpdated(uint256 accTokensPerShare, uint256 stakedSupply);
              event FailEscrowRewards(bytes revertReason);
          
              constructor(
                address ancestor_,
                address descendant_,
                address rewardToken_,
                address auctionHouse_,
                address accountingEngine_,
                address safeEngine_,
                address rewardDripper_,
                address escrow_,
                uint256 maxDelay_,
                uint256 exitDelay_,
                uint256 minStakedTokensToKeep_,
                uint256 tokensToAuction_,
                uint256 systemCoinsToRequest_,
                uint256 percentageVested_
              ) public {
                  require(maxDelay_ > 0, "GebLenderFirstResortRewardsVested/null-max-delay");
                  require(exitDelay_ <= maxDelay_, "GebLenderFirstResortRewardsVested/invalid-exit-delay");
                  require(minStakedTokensToKeep_ > 0, "GebLenderFirstResortRewardsVested/null-min-staked-tokens");
                  require(tokensToAuction_ > 0, "GebLenderFirstResortRewardsVested/null-tokens-to-auction");
                  require(systemCoinsToRequest_ > 0, "GebLenderFirstResortRewardsVested/null-sys-coins-to-request");
                  require(auctionHouse_ != address(0), "GebLenderFirstResortRewardsVested/null-auction-house");
                  require(accountingEngine_ != address(0), "GebLenderFirstResortRewardsVested/null-accounting-engine");
                  require(safeEngine_ != address(0), "GebLenderFirstResortRewardsVested/null-safe-engine");
                  require(rewardDripper_ != address(0), "GebLenderFirstResortRewardsVested/null-reward-dripper");
                  require(escrow_ != address(0), "GebLenderFirstResortRewardsVested/null-escrow");
                  require(percentageVested_ < 100, "GebLenderFirstResortRewardsVested/invalid-percentage-vested");
                  require(descendant_ != address(0), "GebLenderFirstResortRewardsVested/null-descendant");
          
                  authorizedAccounts[msg.sender] = 1;
                  canJoin                        = true;
                  maxConcurrentAuctions          = uint(-1);
          
                  MAX_DELAY                      = maxDelay_;
          
                  exitDelay                      = exitDelay_;
          
                  minStakedTokensToKeep          = minStakedTokensToKeep_;
                  tokensToAuction                = tokensToAuction_;
                  systemCoinsToRequest           = systemCoinsToRequest_;
                  percentageVested               = percentageVested_;
          
                  auctionHouse                   = AuctionHouseLike(auctionHouse_);
                  accountingEngine               = AccountingEngineLike(accountingEngine_);
                  safeEngine                     = SAFEEngineLike(safeEngine_);
                  rewardDripper                  = RewardDripperLike(rewardDripper_);
                  escrow                         = StakingRewardsEscrowLike(escrow_);
                  descendant                     = TokenLike(descendant_);
          
                  ancestorPool                   = new TokenPool(ancestor_);
                  rewardPool                     = new TokenPool(rewardToken_);
          
                  lastRewardBlock                = block.number;
          
                  require(ancestorPool.token().decimals() == 18, "GebLenderFirstResortRewardsVested/ancestor-decimal-mismatch");
                  require(descendant.decimals() == 18, "GebLenderFirstResortRewardsVested/descendant-decimal-mismatch");
          
                  emit AddAuthorization(msg.sender);
              }
          
              // --- Boolean Logic ---
              function both(bool x, bool y) internal pure returns (bool z) {
                  assembly{ z := and(x, y)}
              }
              function either(bool x, bool y) internal pure returns (bool z) {
                  assembly{ z := or(x, y)}
              }
          
              // --- Math ---
              uint256 public constant WAD = 10 ** 18;
              uint256 public constant RAY = 10 ** 27;
          
              function addition(uint256 x, uint256 y) internal pure returns (uint256 z) {
                  require((z = x + y) >= x, "GebLenderFirstResortRewardsVested/add-overflow");
              }
              function subtract(uint256 x, uint256 y) internal pure returns (uint256 z) {
                  require((z = x - y) <= x, "GebLenderFirstResortRewardsVested/sub-underflow");
              }
              function multiply(uint x, uint y) internal pure returns (uint z) {
                  require(y == 0 || (z = x * y) / y == x, "GebLenderFirstResortRewardsVested/mul-overflow");
              }
              function wdivide(uint x, uint y) internal pure returns (uint z) {
                  require(y > 0, "GebLenderFirstResortRewardsVested/wdiv-by-zero");
                  z = multiply(x, WAD) / y;
              }
              function wmultiply(uint x, uint y) internal pure returns (uint z) {
                  z = multiply(x, y) / WAD;
              }
          
              // --- Administration ---
              /*
              * @notify Switch between allowing and disallowing joins
              */
              function toggleJoin() external isAuthorized {
                  canJoin = !canJoin;
                  emit ToggleJoin(canJoin);
              }
              /*
              * @notify Switch between ignoring and taking into account auctions in canPrintProtocolTokens
              */
              function toggleBypassAuctions() external isAuthorized {
                  bypassAuctions = !bypassAuctions;
                  emit ToggleBypassAuctions(bypassAuctions);
              }
              /*
              * @notify Switch between allowing exits when the system is underwater or blocking them
              */
              function toggleForcedExit() external isAuthorized {
                  forcedExit = !forcedExit;
                  emit ToggleForcedExit(forcedExit);
              }
              /*
              * @notify Modify an uint256 parameter
              * @param parameter The name of the parameter to modify
              * @param data New value for the parameter
              */
              function modifyParameters(bytes32 parameter, uint256 data) external isAuthorized {
                  if (parameter == "exitDelay") {
                    require(data <= MAX_DELAY, "GebLenderFirstResortRewardsVested/invalid-exit-delay");
                    exitDelay = data;
                  }
                  else if (parameter == "minStakedTokensToKeep") {
                    require(data > 0, "GebLenderFirstResortRewardsVested/null-min-staked-tokens");
                    minStakedTokensToKeep = data;
                  }
                  else if (parameter == "tokensToAuction") {
                    require(data > 0, "GebLenderFirstResortRewardsVested/invalid-tokens-to-auction");
                    tokensToAuction = data;
                  }
                  else if (parameter == "systemCoinsToRequest") {
                    require(data > 0, "GebLenderFirstResortRewardsVested/invalid-sys-coins-to-request");
                    systemCoinsToRequest = data;
                  }
                  else if (parameter == "maxConcurrentAuctions") {
                    require(data > 1, "GebLenderFirstResortRewardsVested/invalid-max-concurrent-auctions");
                    maxConcurrentAuctions = data;
                  }
                  else if (parameter == "escrowPaused") {
                    require(data <= 1, "GebLenderFirstResortRewardsVested/invalid-escrow-paused");
                    escrowPaused = data;
                  }
                  else if (parameter == "percentageVested") {
                    require(data < 100, "GebLenderFirstResortRewardsVested/invalid-percentage-vested");
                    percentageVested = data;
                  }
                  else revert("GebLenderFirstResortRewardsVested/modify-unrecognized-param");
                  emit ModifyParameters(parameter, data);
              }
              /*
              * @notify Modify an address parameter
              * @param parameter The name of the parameter to modify
              * @param data New value for the parameter
              */
              function modifyParameters(bytes32 parameter, address data) external isAuthorized {
                  require(data != address(0), "GebLenderFirstResortRewardsVested/null-data");
          
                  if (parameter == "auctionHouse") {
                    auctionHouse = AuctionHouseLike(data);
                  }
                  else if (parameter == "accountingEngine") {
                    accountingEngine = AccountingEngineLike(data);
                  }
                  else if (parameter == "rewardDripper") {
                    rewardDripper = RewardDripperLike(data);
                  }
                  else if (parameter == "escrow") {
                    escrow = StakingRewardsEscrowLike(data);
                  }
                  else revert("GebLenderFirstResortRewardsVested/modify-unrecognized-param");
                  emit ModifyParameters(parameter, data);
              }
          
              // --- Getters ---
              /*
              * @notify Return the ancestor token balance for this contract
              */
              function depositedAncestor() public view returns (uint256) {
                  return ancestorPool.balance();
              }
              /*
              * @notify Returns how many ancestor tokens are offered for one descendant token
              */
              function ancestorPerDescendant() public view returns (uint256) {
                  return stakedSupply == 0 ? WAD : wdivide(depositedAncestor(), stakedSupply);
              }
              /*
              * @notify Returns how many descendant tokens are offered for one ancestor token
              */
              function descendantPerAncestor() public view returns (uint256) {
                  return stakedSupply == 0 ? WAD : wdivide(stakedSupply, depositedAncestor());
              }
              /*
              * @notify Given a custom amount of ancestor tokens, it returns the corresponding amount of descendant tokens to mint when someone joins
              * @param wad The amount of ancestor tokens to compute the descendant tokens for
              */
              function joinPrice(uint256 wad) public view returns (uint256) {
                  return wmultiply(wad, descendantPerAncestor());
              }
              /*
              * @notify Given a custom amount of descendant tokens, it returns the corresponding amount of ancestor tokens to send when someone exits
              * @param wad The amount of descendant tokens to compute the ancestor tokens for
              */
              function exitPrice(uint256 wad) public view returns (uint256) {
                  return wmultiply(wad, ancestorPerDescendant());
              }
          
              /*
              * @notice Returns whether the protocol is underwater or not
              */
              function protocolUnderwater() public view returns (bool) {
                  uint256 unqueuedUnauctionedDebt = accountingEngine.unqueuedUnauctionedDebt();
          
                  return both(
                    accountingEngine.debtAuctionBidSize() <= unqueuedUnauctionedDebt,
                    safeEngine.coinBalance(address(accountingEngine)) < unqueuedUnauctionedDebt
                  );
              }
          
              /*
              * @notice Burn descendant tokens in exchange for getting ancestor tokens from this contract
              * @return Whether the pool can auction ancestor tokens
              */
              function canAuctionTokens() public view returns (bool) {
                  return both(
                    both(protocolUnderwater(), addition(minStakedTokensToKeep, tokensToAuction) <= depositedAncestor()),
                    auctionHouse.activeStakedTokenAuctions() < maxConcurrentAuctions
                  );
              }
          
              /*
              * @notice Returns whether the system can mint new ancestor tokens
              */
              function canPrintProtocolTokens() public view returns (bool) {
                  return both(
                    !canAuctionTokens(),
                    either(auctionHouse.activeStakedTokenAuctions() == 0, bypassAuctions)
                  );
              }
          
              /*
              * @notice Returns unclaimed rewards for a given user
              */
              function pendingRewards(address user) public view returns (uint256) {
                  uint accTokensPerShare_ = accTokensPerShare;
                  if (block.number > lastRewardBlock && stakedSupply != 0) {
                      uint increaseInBalance = (block.number - lastRewardBlock) * rewardDripper.rewardPerBlock();
                      accTokensPerShare_ = addition(accTokensPerShare_, multiply(increaseInBalance, RAY) / stakedSupply);
                  }
                  return subtract(multiply(descendantBalanceOf[user], accTokensPerShare_) / RAY, rewardDebt[user]);
              }
          
              /*
              * @notice Returns rewards earned per block for each token deposited (WAD)
              */
              function rewardRate() public view returns (uint256) {
                  if (stakedSupply == 0) return 0;
                  return (rewardDripper.rewardPerBlock() * WAD) / stakedSupply;
              }
          
              // --- Core Logic ---
              /*
              * @notify Updates the pool and pays rewards (if any)
              * @dev Must be included in deposits and withdrawals
              */
              modifier payRewards() {
                  updatePool();
          
                  if (descendantBalanceOf[msg.sender] > 0 && rewardPool.balance() > 0) {
                      // Pays the reward
                      uint256 pending = subtract(multiply(descendantBalanceOf[msg.sender], accTokensPerShare) / RAY, rewardDebt[msg.sender]);
          
                      uint256 vested;
                      if (both(address(escrow) != address(0), escrowPaused == 0)) {
                        vested = multiply(pending, percentageVested) / 100;
          
                        try escrow.escrowRewards(msg.sender, vested) {
                          rewardPool.transfer(address(escrow), vested);
                          emit EscrowRewards(address(escrow), msg.sender, vested);
                        } catch(bytes memory revertReason) {
                          emit FailEscrowRewards(revertReason);
                        }
                      }
          
                      rewardPool.transfer(msg.sender, subtract(pending, vested));
                      rewardsBalance = rewardPool.balance();
          
                      emit RewardsPaid(msg.sender, pending);
                  }
                  _;
          
                  rewardDebt[msg.sender] = multiply(descendantBalanceOf[msg.sender], accTokensPerShare) / RAY;
              }
          
              /*
              * @notify Pays outstanding rewards to msg.sender
              */
              function getRewards() external nonReentrant payRewards {}
          
              /*
              * @notify Pull funds from the dripper
              */
              function pullFunds() public {
                  rewardDripper.dripReward(address(rewardPool));
              }
          
              /*
              * @notify Updates pool data
              */
              function updatePool() public {
                  if (block.number <= lastRewardBlock) return;
                  lastRewardBlock = block.number;
                  if (stakedSupply == 0) return;
          
                  pullFunds();
                  uint256 increaseInBalance = subtract(rewardPool.balance(), rewardsBalance);
                  rewardsBalance = addition(rewardsBalance, increaseInBalance);
          
                  // Updates distribution info
                  accTokensPerShare = addition(accTokensPerShare, multiply(increaseInBalance, RAY) / stakedSupply);
                  emit PoolUpdated(accTokensPerShare, stakedSupply);
              }
          
              /*
              * @notify Create a new auction that sells ancestor tokens in exchange for system coins
              */
              function auctionAncestorTokens() external nonReentrant {
                  require(canAuctionTokens(), "GebLenderFirstResortRewardsVested/cannot-auction-tokens");
          
                  ancestorPool.transfer(address(this), tokensToAuction);
                  ancestorPool.token().approve(address(auctionHouse), tokensToAuction);
                  auctionHouse.startAuction(tokensToAuction, systemCoinsToRequest);
                  updatePool();
          
                  emit AuctionAncestorTokens(address(auctionHouse), tokensToAuction, systemCoinsToRequest);
              }
          
              /*
              * @notify Join ancestor tokens
              * @param wad The amount of ancestor tokens to join
              */
              function join(uint256 wad) external nonReentrant payRewards {
                  require(both(canJoin, !protocolUnderwater()), "GebLenderFirstResortRewardsVested/join-not-allowed");
                  require(wad > 0, "GebLenderFirstResortRewardsVested/null-ancestor-to-join");
                  uint256 price = joinPrice(wad);
                  require(price > 0, "GebLenderFirstResortRewardsVested/null-join-price");
          
                  require(ancestorPool.token().transferFrom(msg.sender, address(ancestorPool), wad), "GebLenderFirstResortRewardsVested/could-not-transfer-ancestor");
                  descendant.mint(msg.sender, price);
          
                  descendantBalanceOf[msg.sender] = addition(descendantBalanceOf[msg.sender], price);
                  stakedSupply = addition(stakedSupply, price);
          
                  emit Join(msg.sender, price, wad);
              }
              /*
              * @notice Request an exit for a specific amount of ancestor tokens
              * @param wad The amount of tokens to exit
              */
              function requestExit(uint wad) external nonReentrant payRewards {
                  require(wad > 0, "GebLenderFirstResortRewardsVested/null-amount-to-exit");
          
                  exitRequests[msg.sender].deadline      = addition(now, exitDelay);
                  exitRequests[msg.sender].lockedAmount  = addition(exitRequests[msg.sender].lockedAmount, wad);
          
                  descendantBalanceOf[msg.sender] = subtract(descendantBalanceOf[msg.sender], wad);
                  descendant.burn(msg.sender, wad);
          
                  emit RequestExit(msg.sender, exitRequests[msg.sender].deadline, wad);
              }
              /*
              * @notify Exit ancestor tokens
              */
              function exit() external nonReentrant {
                  require(both(now >= exitRequests[msg.sender].deadline, exitRequests[msg.sender].lockedAmount > 0), "GebLenderFirstResortRewardsVested/wait-more");
                  require(either(!protocolUnderwater(), forcedExit), "GebLenderFirstResortRewardsVested/exit-not-allowed");
          
                  uint256 price = exitPrice(exitRequests[msg.sender].lockedAmount);
                  stakedSupply  = subtract(stakedSupply, exitRequests[msg.sender].lockedAmount);
                  ancestorPool.transfer(msg.sender, price);
                  emit Exit(msg.sender, price, exitRequests[msg.sender].lockedAmount);
                  delete exitRequests[msg.sender];
              }
          }

          File 2 of 5: TokenPool
          // SPDX-License-Identifier: GPL-3.0
          
          pragma solidity 0.6.7;
          
          abstract contract TokenLike {
              function decimals() virtual public view returns (uint8);
              function totalSupply() virtual public view returns (uint256);
              function balanceOf(address) virtual public view returns (uint256);
              function mint(address, uint) virtual public;
              function burn(address, uint) virtual public;
              function approve(address, uint256) virtual external returns (bool);
              function transfer(address, uint256) virtual external returns (bool);
              function transferFrom(address,address,uint256) virtual external returns (bool);
          }
          
          contract TokenPool {
              TokenLike public token;
              address   public owner;
          
              constructor(address token_) public {
                  token = TokenLike(token_);
                  owner = msg.sender;
              }
          
              // @notice Transfers tokens from the pool (callable by owner only)
              function transfer(address to, uint256 wad) public {
                  require(msg.sender == owner, "unauthorized");
                  require(token.transfer(to, wad), "TokenPool/failed-transfer");
              }
          
              // @notice Returns token balance of the pool
              function balance() public view returns (uint256) {
                  return token.balanceOf(address(this));
              }
          }

          File 3 of 5: UniswapV2Pair
          // File: contracts/interfaces/IUniswapV2Pair.sol
          
          pragma solidity >=0.5.0;
          
          interface IUniswapV2Pair {
              event Approval(address indexed owner, address indexed spender, uint value);
              event Transfer(address indexed from, address indexed to, uint value);
          
              function name() external pure returns (string memory);
              function symbol() external pure returns (string memory);
              function decimals() external pure returns (uint8);
              function totalSupply() external view returns (uint);
              function balanceOf(address owner) external view returns (uint);
              function allowance(address owner, address spender) external view returns (uint);
          
              function approve(address spender, uint value) external returns (bool);
              function transfer(address to, uint value) external returns (bool);
              function transferFrom(address from, address to, uint value) external returns (bool);
          
              function DOMAIN_SEPARATOR() external view returns (bytes32);
              function PERMIT_TYPEHASH() external pure returns (bytes32);
              function nonces(address owner) external view returns (uint);
          
              function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
          
              event Mint(address indexed sender, uint amount0, uint amount1);
              event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
              event Swap(
                  address indexed sender,
                  uint amount0In,
                  uint amount1In,
                  uint amount0Out,
                  uint amount1Out,
                  address indexed to
              );
              event Sync(uint112 reserve0, uint112 reserve1);
          
              function MINIMUM_LIQUIDITY() external pure returns (uint);
              function factory() external view returns (address);
              function token0() external view returns (address);
              function token1() external view returns (address);
              function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
              function price0CumulativeLast() external view returns (uint);
              function price1CumulativeLast() external view returns (uint);
              function kLast() external view returns (uint);
          
              function mint(address to) external returns (uint liquidity);
              function burn(address to) external returns (uint amount0, uint amount1);
              function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
              function skim(address to) external;
              function sync() external;
          
              function initialize(address, address) external;
          }
          
          // File: contracts/interfaces/IUniswapV2ERC20.sol
          
          pragma solidity >=0.5.0;
          
          interface IUniswapV2ERC20 {
              event Approval(address indexed owner, address indexed spender, uint value);
              event Transfer(address indexed from, address indexed to, uint value);
          
              function name() external pure returns (string memory);
              function symbol() external pure returns (string memory);
              function decimals() external pure returns (uint8);
              function totalSupply() external view returns (uint);
              function balanceOf(address owner) external view returns (uint);
              function allowance(address owner, address spender) external view returns (uint);
          
              function approve(address spender, uint value) external returns (bool);
              function transfer(address to, uint value) external returns (bool);
              function transferFrom(address from, address to, uint value) external returns (bool);
          
              function DOMAIN_SEPARATOR() external view returns (bytes32);
              function PERMIT_TYPEHASH() external pure returns (bytes32);
              function nonces(address owner) external view returns (uint);
          
              function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
          }
          
          // File: contracts/libraries/SafeMath.sol
          
          pragma solidity =0.5.16;
          
          // a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math)
          
          library SafeMath {
              function add(uint x, uint y) internal pure returns (uint z) {
                  require((z = x + y) >= x, 'ds-math-add-overflow');
              }
          
              function sub(uint x, uint y) internal pure returns (uint z) {
                  require((z = x - y) <= x, 'ds-math-sub-underflow');
              }
          
              function mul(uint x, uint y) internal pure returns (uint z) {
                  require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow');
              }
          }
          
          // File: contracts/UniswapV2ERC20.sol
          
          pragma solidity =0.5.16;
          
          
          
          contract UniswapV2ERC20 is IUniswapV2ERC20 {
              using SafeMath for uint;
          
              string public constant name = 'Uniswap V2';
              string public constant symbol = 'UNI-V2';
              uint8 public constant decimals = 18;
              uint  public totalSupply;
              mapping(address => uint) public balanceOf;
              mapping(address => mapping(address => uint)) public allowance;
          
              bytes32 public DOMAIN_SEPARATOR;
              // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
              bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
              mapping(address => uint) public nonces;
          
              event Approval(address indexed owner, address indexed spender, uint value);
              event Transfer(address indexed from, address indexed to, uint value);
          
              constructor() public {
                  uint chainId;
                  assembly {
                      chainId := chainid
                  }
                  DOMAIN_SEPARATOR = keccak256(
                      abi.encode(
                          keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
                          keccak256(bytes(name)),
                          keccak256(bytes('1')),
                          chainId,
                          address(this)
                      )
                  );
              }
          
              function _mint(address to, uint value) internal {
                  totalSupply = totalSupply.add(value);
                  balanceOf[to] = balanceOf[to].add(value);
                  emit Transfer(address(0), to, value);
              }
          
              function _burn(address from, uint value) internal {
                  balanceOf[from] = balanceOf[from].sub(value);
                  totalSupply = totalSupply.sub(value);
                  emit Transfer(from, address(0), value);
              }
          
              function _approve(address owner, address spender, uint value) private {
                  allowance[owner][spender] = value;
                  emit Approval(owner, spender, value);
              }
          
              function _transfer(address from, address to, uint value) private {
                  balanceOf[from] = balanceOf[from].sub(value);
                  balanceOf[to] = balanceOf[to].add(value);
                  emit Transfer(from, to, value);
              }
          
              function approve(address spender, uint value) external returns (bool) {
                  _approve(msg.sender, spender, value);
                  return true;
              }
          
              function transfer(address to, uint value) external returns (bool) {
                  _transfer(msg.sender, to, value);
                  return true;
              }
          
              function transferFrom(address from, address to, uint value) external returns (bool) {
                  if (allowance[from][msg.sender] != uint(-1)) {
                      allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);
                  }
                  _transfer(from, to, value);
                  return true;
              }
          
              function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
                  require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
                  bytes32 digest = keccak256(
                      abi.encodePacked(
                          '\x19\x01',
                          DOMAIN_SEPARATOR,
                          keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
                      )
                  );
                  address recoveredAddress = ecrecover(digest, v, r, s);
                  require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
                  _approve(owner, spender, value);
              }
          }
          
          // File: contracts/libraries/Math.sol
          
          pragma solidity =0.5.16;
          
          // a library for performing various math operations
          
          library Math {
              function min(uint x, uint y) internal pure returns (uint z) {
                  z = x < y ? x : y;
              }
          
              // babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
              function sqrt(uint y) internal pure returns (uint z) {
                  if (y > 3) {
                      z = y;
                      uint x = y / 2 + 1;
                      while (x < z) {
                          z = x;
                          x = (y / x + x) / 2;
                      }
                  } else if (y != 0) {
                      z = 1;
                  }
              }
          }
          
          // File: contracts/libraries/UQ112x112.sol
          
          pragma solidity =0.5.16;
          
          // a library for handling binary fixed point numbers (https://en.wikipedia.org/wiki/Q_(number_format))
          
          // range: [0, 2**112 - 1]
          // resolution: 1 / 2**112
          
          library UQ112x112 {
              uint224 constant Q112 = 2**112;
          
              // encode a uint112 as a UQ112x112
              function encode(uint112 y) internal pure returns (uint224 z) {
                  z = uint224(y) * Q112; // never overflows
              }
          
              // divide a UQ112x112 by a uint112, returning a UQ112x112
              function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {
                  z = x / uint224(y);
              }
          }
          
          // File: contracts/interfaces/IERC20.sol
          
          pragma solidity >=0.5.0;
          
          interface IERC20 {
              event Approval(address indexed owner, address indexed spender, uint value);
              event Transfer(address indexed from, address indexed to, uint value);
          
              function name() external view returns (string memory);
              function symbol() external view returns (string memory);
              function decimals() external view returns (uint8);
              function totalSupply() external view returns (uint);
              function balanceOf(address owner) external view returns (uint);
              function allowance(address owner, address spender) external view returns (uint);
          
              function approve(address spender, uint value) external returns (bool);
              function transfer(address to, uint value) external returns (bool);
              function transferFrom(address from, address to, uint value) external returns (bool);
          }
          
          // File: contracts/interfaces/IUniswapV2Factory.sol
          
          pragma solidity >=0.5.0;
          
          interface IUniswapV2Factory {
              event PairCreated(address indexed token0, address indexed token1, address pair, uint);
          
              function feeTo() external view returns (address);
              function feeToSetter() external view returns (address);
          
              function getPair(address tokenA, address tokenB) external view returns (address pair);
              function allPairs(uint) external view returns (address pair);
              function allPairsLength() external view returns (uint);
          
              function createPair(address tokenA, address tokenB) external returns (address pair);
          
              function setFeeTo(address) external;
              function setFeeToSetter(address) external;
          }
          
          // File: contracts/interfaces/IUniswapV2Callee.sol
          
          pragma solidity >=0.5.0;
          
          interface IUniswapV2Callee {
              function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external;
          }
          
          // File: contracts/UniswapV2Pair.sol
          
          pragma solidity =0.5.16;
          
          
          
          
          
          
          
          
          contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {
              using SafeMath  for uint;
              using UQ112x112 for uint224;
          
              uint public constant MINIMUM_LIQUIDITY = 10**3;
              bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));
          
              address public factory;
              address public token0;
              address public token1;
          
              uint112 private reserve0;           // uses single storage slot, accessible via getReserves
              uint112 private reserve1;           // uses single storage slot, accessible via getReserves
              uint32  private blockTimestampLast; // uses single storage slot, accessible via getReserves
          
              uint public price0CumulativeLast;
              uint public price1CumulativeLast;
              uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event
          
              uint private unlocked = 1;
              modifier lock() {
                  require(unlocked == 1, 'UniswapV2: LOCKED');
                  unlocked = 0;
                  _;
                  unlocked = 1;
              }
          
              function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
                  _reserve0 = reserve0;
                  _reserve1 = reserve1;
                  _blockTimestampLast = blockTimestampLast;
              }
          
              function _safeTransfer(address token, address to, uint value) private {
                  (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));
                  require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');
              }
          
              event Mint(address indexed sender, uint amount0, uint amount1);
              event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
              event Swap(
                  address indexed sender,
                  uint amount0In,
                  uint amount1In,
                  uint amount0Out,
                  uint amount1Out,
                  address indexed to
              );
              event Sync(uint112 reserve0, uint112 reserve1);
          
              constructor() public {
                  factory = msg.sender;
              }
          
              // called once by the factory at time of deployment
              function initialize(address _token0, address _token1) external {
                  require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
                  token0 = _token0;
                  token1 = _token1;
              }
          
              // update reserves and, on the first call per block, price accumulators
              function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
                  require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');
                  uint32 blockTimestamp = uint32(block.timestamp % 2**32);
                  uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired
                  if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
                      // * never overflows, and + overflow is desired
                      price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
                      price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
                  }
                  reserve0 = uint112(balance0);
                  reserve1 = uint112(balance1);
                  blockTimestampLast = blockTimestamp;
                  emit Sync(reserve0, reserve1);
              }
          
              // if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k)
              function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
                  address feeTo = IUniswapV2Factory(factory).feeTo();
                  feeOn = feeTo != address(0);
                  uint _kLast = kLast; // gas savings
                  if (feeOn) {
                      if (_kLast != 0) {
                          uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
                          uint rootKLast = Math.sqrt(_kLast);
                          if (rootK > rootKLast) {
                              uint numerator = totalSupply.mul(rootK.sub(rootKLast));
                              uint denominator = rootK.mul(5).add(rootKLast);
                              uint liquidity = numerator / denominator;
                              if (liquidity > 0) _mint(feeTo, liquidity);
                          }
                      }
                  } else if (_kLast != 0) {
                      kLast = 0;
                  }
              }
          
              // this low-level function should be called from a contract which performs important safety checks
              function mint(address to) external lock returns (uint liquidity) {
                  (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
                  uint balance0 = IERC20(token0).balanceOf(address(this));
                  uint balance1 = IERC20(token1).balanceOf(address(this));
                  uint amount0 = balance0.sub(_reserve0);
                  uint amount1 = balance1.sub(_reserve1);
          
                  bool feeOn = _mintFee(_reserve0, _reserve1);
                  uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
                  if (_totalSupply == 0) {
                      liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
                     _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
                  } else {
                      liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
                  }
                  require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
                  _mint(to, liquidity);
          
                  _update(balance0, balance1, _reserve0, _reserve1);
                  if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
                  emit Mint(msg.sender, amount0, amount1);
              }
          
              // this low-level function should be called from a contract which performs important safety checks
              function burn(address to) external lock returns (uint amount0, uint amount1) {
                  (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
                  address _token0 = token0;                                // gas savings
                  address _token1 = token1;                                // gas savings
                  uint balance0 = IERC20(_token0).balanceOf(address(this));
                  uint balance1 = IERC20(_token1).balanceOf(address(this));
                  uint liquidity = balanceOf[address(this)];
          
                  bool feeOn = _mintFee(_reserve0, _reserve1);
                  uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
                  amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
                  amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
                  require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
                  _burn(address(this), liquidity);
                  _safeTransfer(_token0, to, amount0);
                  _safeTransfer(_token1, to, amount1);
                  balance0 = IERC20(_token0).balanceOf(address(this));
                  balance1 = IERC20(_token1).balanceOf(address(this));
          
                  _update(balance0, balance1, _reserve0, _reserve1);
                  if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
                  emit Burn(msg.sender, amount0, amount1, to);
              }
          
              // this low-level function should be called from a contract which performs important safety checks
              function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
                  require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
                  (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
                  require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
          
                  uint balance0;
                  uint balance1;
                  { // scope for _token{0,1}, avoids stack too deep errors
                  address _token0 = token0;
                  address _token1 = token1;
                  require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
                  if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
                  if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
                  if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
                  balance0 = IERC20(_token0).balanceOf(address(this));
                  balance1 = IERC20(_token1).balanceOf(address(this));
                  }
                  uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
                  uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
                  require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
                  { // scope for reserve{0,1}Adjusted, avoids stack too deep errors
                  uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
                  uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
                  require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
                  }
          
                  _update(balance0, balance1, _reserve0, _reserve1);
                  emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
              }
          
              // force balances to match reserves
              function skim(address to) external lock {
                  address _token0 = token0; // gas savings
                  address _token1 = token1; // gas savings
                  _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));
                  _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));
              }
          
              // force reserves to match balances
              function sync() external lock {
                  _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
              }
          }

          File 4 of 5: AccountingEngine
          /**
           *Submitted for verification at Etherscan.io on 2021-02-02
          */
          
          /// AccountingEngine.sol
          
          // Copyright (C) 2018 Rain <[email protected]>
          //
          // This program is free software: you can redistribute it and/or modify
          // it under the terms of the GNU Affero General Public License as published by
          // the Free Software Foundation, either version 3 of the License, or
          // (at your option) any later version.
          //
          // This program is distributed in the hope that it will be useful,
          // but WITHOUT ANY WARRANTY; without even the implied warranty of
          // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
          // GNU Affero General Public License for more details.
          //
          // You should have received a copy of the GNU Affero General Public License
          // along with this program.  If not, see <https://www.gnu.org/licenses/>.
          
          pragma solidity 0.6.7;
          
          abstract contract DebtAuctionHouseLike {
              function startAuction(address incomeReceiver, uint256 amountToSell, uint256 initialBid) virtual public returns (uint256);
              function protocolToken() virtual public view returns (address);
              function disableContract() virtual external;
              function contractEnabled() virtual public view returns (uint256);
          }
          
          abstract contract SurplusAuctionHouseLike {
              function startAuction(uint256, uint256) virtual public returns (uint256);
              function protocolToken() virtual public view returns (address);
              function disableContract() virtual external;
              function contractEnabled() virtual public view returns (uint256);
          }
          
          abstract contract SAFEEngineLike {
              function coinBalance(address) virtual public view returns (uint256);
              function debtBalance(address) virtual public view returns (uint256);
              function settleDebt(uint256) virtual external;
              function transferInternalCoins(address,address,uint256) virtual external;
              function approveSAFEModification(address) virtual external;
              function denySAFEModification(address) virtual external;
          }
          
          abstract contract SystemStakingPoolLike {
              function canPrintProtocolTokens() virtual public view returns (bool);
          }
          
          abstract contract ProtocolTokenAuthorityLike {
              function authorizedAccounts(address) virtual public view returns (uint256);
          }
          
          contract AccountingEngine {
              // --- Auth ---
              mapping (address => uint256) public authorizedAccounts;
              /**
               * @notice Add auth to an account
               * @param account Account to add auth to
               */
              function addAuthorization(address account) external isAuthorized {
                  require(contractEnabled == 1, "AccountingEngine/contract-not-enabled");
                  authorizedAccounts[account] = 1;
                  emit AddAuthorization(account);
              }
              /**
               * @notice Remove auth from an account
               * @param account Account to remove auth from
               */
              function removeAuthorization(address account) external isAuthorized {
                  authorizedAccounts[account] = 0;
                  emit RemoveAuthorization(account);
              }
              /**
              * @notice Checks whether msg.sender can call an authed function
              **/
              modifier isAuthorized {
                  require(authorizedAccounts[msg.sender] == 1, "AccountingEngine/account-not-authorized");
                  _;
              }
          
              // --- Data ---
              // SAFE database
              SAFEEngineLike             public safeEngine;
              // Contract that handles auctions for surplus stability fees (sell coins for protocol tokens that are then burned)
              SurplusAuctionHouseLike    public surplusAuctionHouse;
              /**
                Contract that handles auctions for debt that couldn't be covered by collateral
                auctions (it prints protocol tokens in exchange for coins that will settle the debt)
              **/
              DebtAuctionHouseLike       public debtAuctionHouse;
              // Permissions registry for who can burn and mint protocol tokens
              ProtocolTokenAuthorityLike public protocolTokenAuthority;
              // Staking pool for protocol tokens
              SystemStakingPoolLike      public systemStakingPool;
              // Contract that auctions extra surplus after settlement is triggered
              address                    public postSettlementSurplusDrain;
              // Address that receives extra surplus transfers
              address                    public extraSurplusReceiver;
          
              /**
                Debt blocks that need to be covered by auctions. There is a delay to pop debt from
                this queue and either settle it with surplus that came from collateral auctions or with debt auctions
                that print protocol tokens
              **/
              mapping (uint256 => uint256) public debtQueue;          // [unix timestamp => rad]
              // Addresses that popped debt out of the queue
              mapping (uint256 => address) public debtPoppers;        // [unix timestamp => address]
              // Total debt in the queue (that the system tries to cover with collateral auctions)
              uint256 public totalQueuedDebt;                         // [rad]
              // Total debt being auctioned in DebtAuctionHouse (printing protocol tokens for coins that will settle the debt)
              uint256 public totalOnAuctionDebt;                      // [rad]
              // When the last surplus auction was triggered
              uint256 public lastSurplusAuctionTime;                  // [unix timestamp]
              // When the last surplus transfer was triggered
              uint256 public lastSurplusTransferTime;                 // [unix timestamp]
              // Delay between surplus auctions
              uint256 public surplusAuctionDelay;                     // [seconds]
              // Delay between extra surplus transfers
              uint256 public surplusTransferDelay;                    // [seconds]
              // Delay after which debt can be popped from debtQueue
              uint256 public popDebtDelay;                            // [seconds]
              // Amount of protocol tokens to be minted post-auction
              uint256 public initialDebtAuctionMintedTokens;          // [wad]
              // Amount of debt sold in one debt auction (initial coin bid for initialDebtAuctionMintedTokens protocol tokens)
              uint256 public debtAuctionBidSize;                      // [rad]
          
              // Whether the system transfers surplus instead of auctioning it
              uint256 public extraSurplusIsTransferred;
              // Amount of surplus stability fees sold in one surplus auction
              uint256 public surplusAuctionAmountToSell;              // [rad]
              // Amount of extra surplus to transfer
              uint256 public surplusTransferAmount;                   // [rad]
              // Amount of stability fees that need to accrue in this contract before any surplus auction can start
              uint256 public surplusBuffer;                           // [rad]
          
              // Time to wait (post settlement) until any remaining surplus can be transferred to the settlement auctioneer
              uint256 public disableCooldown;                         // [seconds]
              // When the contract was disabled
              uint256 public disableTimestamp;                        // [unix timestamp]
          
              // Whether this contract is enabled or not
              uint256 public contractEnabled;
          
              // --- Events ---
              event AddAuthorization(address account);
              event RemoveAuthorization(address account);
              event ModifyParameters(bytes32 indexed parameter, uint256 data);
              event ModifyParameters(bytes32 indexed parameter, address data);
              event PushDebtToQueue(uint256 indexed timestamp, uint256 debtQueueBlock, uint256 totalQueuedDebt);
              event PopDebtFromQueue(uint256 indexed timestamp, uint256 debtQueueBlock, uint256 totalQueuedDebt);
              event SettleDebt(uint256 rad, uint256 coinBalance, uint256 debtBalance);
              event CancelAuctionedDebtWithSurplus(uint rad, uint256 totalOnAuctionDebt, uint256 coinBalance, uint256 debtBalance);
              event AuctionDebt(uint256 indexed id, uint256 totalOnAuctionDebt, uint256 debtBalance);
              event AuctionSurplus(uint256 indexed id, uint256 lastSurplusAuctionTime, uint256 coinBalance);
              event DisableContract(uint256 disableTimestamp, uint256 disableCooldown, uint256 coinBalance, uint256 debtBalance);
              event TransferPostSettlementSurplus(address postSettlementSurplusDrain, uint256 coinBalance, uint256 debtBalance);
              event TransferExtraSurplus(address indexed extraSurplusReceiver, uint256 lastSurplusAuctionTime, uint256 coinBalance);
          
              // --- Init ---
              constructor(
                address safeEngine_,
                address surplusAuctionHouse_,
                address debtAuctionHouse_
              ) public {
                  authorizedAccounts[msg.sender] = 1;
                  safeEngine = SAFEEngineLike(safeEngine_);
                  surplusAuctionHouse = SurplusAuctionHouseLike(surplusAuctionHouse_);
                  debtAuctionHouse = DebtAuctionHouseLike(debtAuctionHouse_);
                  safeEngine.approveSAFEModification(surplusAuctionHouse_);
                  lastSurplusAuctionTime  = now;
                  lastSurplusTransferTime = now;
                  contractEnabled = 1;
                  emit AddAuthorization(msg.sender);
              }
          
              // --- Math ---
              function addition(uint256 x, uint256 y) internal pure returns (uint256 z) {
                  require((z = x + y) >= x, "AccountingEngine/add-overflow");
              }
              function subtract(uint256 x, uint256 y) internal pure returns (uint256 z) {
                  require((z = x - y) <= x, "AccountingEngine/sub-underflow");
              }
              function minimum(uint256 x, uint256 y) internal pure returns (uint256 z) {
                  return x <= y ? x : y;
              }
          
              // --- Administration ---
              /**
               * @notice Modify general uint256 params for auctions
               * @param parameter The name of the parameter modified
               * @param data New value for the parameter
               */
              function modifyParameters(bytes32 parameter, uint256 data) external isAuthorized {
                  if (parameter == "surplusAuctionDelay") surplusAuctionDelay = data;
                  else if (parameter == "surplusTransferDelay") surplusTransferDelay = data;
                  else if (parameter == "popDebtDelay") popDebtDelay = data;
                  else if (parameter == "surplusAuctionAmountToSell") surplusAuctionAmountToSell = data;
                  else if (parameter == "surplusTransferAmount") surplusTransferAmount = data;
                  else if (parameter == "extraSurplusIsTransferred") extraSurplusIsTransferred = data;
                  else if (parameter == "debtAuctionBidSize") debtAuctionBidSize = data;
                  else if (parameter == "initialDebtAuctionMintedTokens") initialDebtAuctionMintedTokens = data;
                  else if (parameter == "surplusBuffer") surplusBuffer = data;
                  else if (parameter == "lastSurplusTransferTime") {
                    require(data > now, "AccountingEngine/invalid-lastSurplusTransferTime");
                    lastSurplusTransferTime = data;
                  }
                  else if (parameter == "lastSurplusAuctionTime") {
                    require(data > now, "AccountingEngine/invalid-lastSurplusAuctionTime");
                    lastSurplusAuctionTime = data;
                  }
                  else if (parameter == "disableCooldown") disableCooldown = data;
                  else revert("AccountingEngine/modify-unrecognized-param");
                  emit ModifyParameters(parameter, data);
              }
              /**
               * @notice Modify dependency addresses
               * @param parameter The name of the auction type we want to change the address for
               * @param data New address for the auction
               */
              function modifyParameters(bytes32 parameter, address data) external isAuthorized {
                  if (parameter == "surplusAuctionHouse") {
                      safeEngine.denySAFEModification(address(surplusAuctionHouse));
                      surplusAuctionHouse = SurplusAuctionHouseLike(data);
                      safeEngine.approveSAFEModification(data);
                  }
                  else if (parameter == "systemStakingPool") {
                      systemStakingPool = SystemStakingPoolLike(data);
                      systemStakingPool.canPrintProtocolTokens();
                  }
                  else if (parameter == "debtAuctionHouse") debtAuctionHouse = DebtAuctionHouseLike(data);
                  else if (parameter == "postSettlementSurplusDrain") postSettlementSurplusDrain = data;
                  else if (parameter == "protocolTokenAuthority") protocolTokenAuthority = ProtocolTokenAuthorityLike(data);
                  else if (parameter == "extraSurplusReceiver") extraSurplusReceiver = data;
                  else revert("AccountingEngine/modify-unrecognized-param");
                  emit ModifyParameters(parameter, data);
              }
          
              // --- Getters ---
              function unqueuedUnauctionedDebt() public view returns (uint256) {
                  return subtract(subtract(safeEngine.debtBalance(address(this)), totalQueuedDebt), totalOnAuctionDebt);
              }
              function canPrintProtocolTokens() public view returns (bool) {
                  if (address(systemStakingPool) == address(0)) return true;
                  try systemStakingPool.canPrintProtocolTokens() returns (bool ok) {
                    return ok;
                  } catch(bytes memory) {
                    return true;
                  }
              }
          
              // --- Debt Queueing ---
              /**
               * @notice Push debt (that the system tries to cover with collateral auctions) to a queue
               * @dev Debt is locked in a queue to give the system enough time to auction collateral
               *      and gather surplus
               * @param debtBlock Amount of debt to push
               */
              function pushDebtToQueue(uint256 debtBlock) external isAuthorized {
                  debtQueue[now] = addition(debtQueue[now], debtBlock);
                  totalQueuedDebt = addition(totalQueuedDebt, debtBlock);
                  emit PushDebtToQueue(now, debtQueue[now], totalQueuedDebt);
              }
              /**
               * @notice A block of debt can be popped from the queue after popDebtDelay seconds passed since it was
               *         added there
               * @param debtBlockTimestamp Timestamp of the block of debt that should be popped out
               */
              function popDebtFromQueue(uint256 debtBlockTimestamp) external {
                  require(addition(debtBlockTimestamp, popDebtDelay) <= now, "AccountingEngine/pop-debt-delay-not-passed");
                  require(debtQueue[debtBlockTimestamp] > 0, "AccountingEngine/null-debt-block");
                  totalQueuedDebt = subtract(totalQueuedDebt, debtQueue[debtBlockTimestamp]);
                  debtPoppers[debtBlockTimestamp] = msg.sender;
                  emit PopDebtFromQueue(now, debtQueue[debtBlockTimestamp], totalQueuedDebt);
                  debtQueue[debtBlockTimestamp] = 0;
              }
          
              // Debt settlement
              /**
               * @notice Destroy an equal amount of coins and debt
               * @dev We can only destroy debt that is not locked in the queue and also not in a debt auction
               * @param rad Amount of coins/debt to destroy (number with 45 decimals)
              **/
              function settleDebt(uint256 rad) public {
                  require(rad <= safeEngine.coinBalance(address(this)), "AccountingEngine/insufficient-surplus");
                  require(rad <= unqueuedUnauctionedDebt(), "AccountingEngine/insufficient-debt");
                  safeEngine.settleDebt(rad);
                  emit SettleDebt(rad, safeEngine.coinBalance(address(this)), safeEngine.debtBalance(address(this)));
              }
              /**
               * @notice Use surplus coins to destroy debt that is/was in a debt auction
               * @param rad Amount of coins/debt to destroy (number with 45 decimals)
              **/
              function cancelAuctionedDebtWithSurplus(uint256 rad) external {
                  require(rad <= totalOnAuctionDebt, "AccountingEngine/not-enough-debt-being-auctioned");
                  require(rad <= safeEngine.coinBalance(address(this)), "AccountingEngine/insufficient-surplus");
                  totalOnAuctionDebt = subtract(totalOnAuctionDebt, rad);
                  safeEngine.settleDebt(rad);
                  emit CancelAuctionedDebtWithSurplus(rad, totalOnAuctionDebt, safeEngine.coinBalance(address(this)), safeEngine.debtBalance(address(this)));
              }
          
              // Debt auction
              /**
               * @notice Start a debt auction (print protocol tokens in exchange for coins so that the
               *         system can accumulate surplus)
               * @dev We can only auction debt that is not already being auctioned and is not locked in the debt queue
              **/
              function auctionDebt() external returns (uint256 id) {
                  require(debtAuctionBidSize <= unqueuedUnauctionedDebt(), "AccountingEngine/insufficient-debt");
                  settleDebt(safeEngine.coinBalance(address(this)));
                  require(safeEngine.coinBalance(address(this)) == 0, "AccountingEngine/surplus-not-zero");
                  require(debtAuctionHouse.protocolToken() != address(0), "AccountingEngine/debt-auction-house-null-prot");
                  require(protocolTokenAuthority.authorizedAccounts(address(debtAuctionHouse)) == 1, "AccountingEngine/debt-auction-house-cannot-print-prot");
                  require(canPrintProtocolTokens(), "AccountingEngine/staking-pool-denies-printing");
                  totalOnAuctionDebt = addition(totalOnAuctionDebt, debtAuctionBidSize);
                  id = debtAuctionHouse.startAuction(address(this), initialDebtAuctionMintedTokens, debtAuctionBidSize);
                  emit AuctionDebt(id, totalOnAuctionDebt, safeEngine.debtBalance(address(this)));
              }
          
              // Surplus auction
              /**
               * @notice Start a surplus auction
               * @dev We can only auction surplus if we wait at least 'surplusAuctionDelay' seconds since the last
               *      auction trigger, if we keep enough surplus in the buffer and if there is no bad debt left to settle
              **/
              function auctionSurplus() external returns (uint256 id) {
                  require(extraSurplusIsTransferred != 1, "AccountingEngine/surplus-transfer-no-auction");
                  require(surplusAuctionAmountToSell > 0, "AccountingEngine/null-amount-to-auction");
                  settleDebt(unqueuedUnauctionedDebt());
                  require(
                    now >= addition(lastSurplusAuctionTime, surplusAuctionDelay),
                    "AccountingEngine/surplus-auction-delay-not-passed"
                  );
                  require(
                    safeEngine.coinBalance(address(this)) >=
                    addition(addition(safeEngine.debtBalance(address(this)), surplusAuctionAmountToSell), surplusBuffer),
                    "AccountingEngine/insufficient-surplus"
                  );
                  require(
                    unqueuedUnauctionedDebt() == 0,
                    "AccountingEngine/debt-not-zero"
                  );
                  require(surplusAuctionHouse.protocolToken() != address(0), "AccountingEngine/surplus-auction-house-null-prot");
                  lastSurplusAuctionTime  = now;
                  lastSurplusTransferTime = now;
                  id = surplusAuctionHouse.startAuction(surplusAuctionAmountToSell, 0);
                  emit AuctionSurplus(id, lastSurplusAuctionTime, safeEngine.coinBalance(address(this)));
              }
          
              // Extra surplus transfers/surplus auction alternative
              /**
               * @notice Send surplus to an address as an alternative to auctions
               * @dev We can only transfer surplus if we wait at least 'surplusTransferDelay' seconds since the last
               *      transfer, if we keep enough surplus in the buffer and if there is no bad debt left to settle
              **/
              function transferExtraSurplus() external {
                  require(extraSurplusIsTransferred == 1, "AccountingEngine/surplus-auction-not-transfer");
                  require(extraSurplusReceiver != address(0), "AccountingEngine/null-surplus-receiver");
                  require(surplusTransferAmount > 0, "AccountingEngine/null-amount-to-transfer");
                  settleDebt(unqueuedUnauctionedDebt());
                  require(
                    now >= addition(lastSurplusTransferTime, surplusTransferDelay),
                    "AccountingEngine/surplus-transfer-delay-not-passed"
                  );
                  require(
                    safeEngine.coinBalance(address(this)) >=
                    addition(addition(safeEngine.debtBalance(address(this)), surplusTransferAmount), surplusBuffer),
                    "AccountingEngine/insufficient-surplus"
                  );
                  require(
                    unqueuedUnauctionedDebt() == 0,
                    "AccountingEngine/debt-not-zero"
                  );
                  lastSurplusTransferTime = now;
                  lastSurplusAuctionTime  = now;
                  safeEngine.transferInternalCoins(address(this), extraSurplusReceiver, surplusTransferAmount);
                  emit TransferExtraSurplus(extraSurplusReceiver, lastSurplusTransferTime, safeEngine.coinBalance(address(this)));
              }
          
              /**
               * @notice Disable this contract (normally called by Global Settlement)
               * @dev When it's disabled, the contract will record the current timestamp. Afterwards,
               *      the contract tries to settle as much debt as possible (if there's any) with any surplus that's
               *      left in the system
              **/
              function disableContract() external isAuthorized {
                  require(contractEnabled == 1, "AccountingEngine/contract-not-enabled");
          
                  contractEnabled = 0;
                  totalQueuedDebt = 0;
                  totalOnAuctionDebt = 0;
          
                  disableTimestamp = now;
          
                  surplusAuctionHouse.disableContract();
                  debtAuctionHouse.disableContract();
          
                  safeEngine.settleDebt(minimum(safeEngine.coinBalance(address(this)), safeEngine.debtBalance(address(this))));
          
                  emit DisableContract(disableTimestamp, disableCooldown, safeEngine.coinBalance(address(this)), safeEngine.debtBalance(address(this)));
              }
              /**
               * @notice Transfer any remaining surplus after the disable cooldown has passed. Meant to be a backup in case GlobalSettlement.processSAFE
                         has a bug, governance doesn't have power over the system and there's still surplus left in the AccountingEngine
                         which then blocks GlobalSettlement.setOutstandingCoinSupply.
               * @dev Transfer any remaining surplus after disableCooldown seconds have passed since disabling the contract
              **/
              function transferPostSettlementSurplus() external {
                  require(contractEnabled == 0, "AccountingEngine/still-enabled");
                  require(addition(disableTimestamp, disableCooldown) <= now, "AccountingEngine/cooldown-not-passed");
                  safeEngine.settleDebt(minimum(safeEngine.coinBalance(address(this)), safeEngine.debtBalance(address(this))));
                  safeEngine.transferInternalCoins(address(this), postSettlementSurplusDrain, safeEngine.coinBalance(address(this)));
                  emit TransferPostSettlementSurplus(
                    postSettlementSurplusDrain,
                    safeEngine.coinBalance(address(this)),
                    safeEngine.debtBalance(address(this))
                  );
              }
          }

          File 5 of 5: SAFEEngine
          /**
           *Submitted for verification at Etherscan.io on 2021-02-02
          */
          
          /// SAFEEngine.sol -- SAFE database
          
          // Copyright (C) 2018 Rain <[email protected]>
          //
          // This program is free software: you can redistribute it and/or modify
          // it under the terms of the GNU Affero General Public License as published by
          // the Free Software Foundation, either version 3 of the License, or
          // (at your option) any later version.
          //
          // This program is distributed in the hope that it will be useful,
          // but WITHOUT ANY WARRANTY; without even the implied warranty of
          // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
          // GNU Affero General Public License for more details.
          //
          // You should have received a copy of the GNU Affero General Public License
          // along with this program.  If not, see <https://www.gnu.org/licenses/>.
          
          pragma solidity 0.6.7;
          
          contract SAFEEngine {
              // --- Auth ---
              mapping (address => uint256) public authorizedAccounts;
              /**
               * @notice Add auth to an account
               * @param account Account to add auth to
               */
              function addAuthorization(address account) external isAuthorized {
                  require(contractEnabled == 1, "SAFEEngine/contract-not-enabled");
                  authorizedAccounts[account] = 1;
                  emit AddAuthorization(account);
              }
              /**
               * @notice Remove auth from an account
               * @param account Account to remove auth from
               */
              function removeAuthorization(address account) external isAuthorized {
                  require(contractEnabled == 1, "SAFEEngine/contract-not-enabled");
                  authorizedAccounts[account] = 0;
                  emit RemoveAuthorization(account);
              }
              /**
              * @notice Checks whether msg.sender can call an authed function
              **/
              modifier isAuthorized {
                  require(authorizedAccounts[msg.sender] == 1, "SAFEEngine/account-not-authorized");
                  _;
              }
          
              // Who can transfer collateral & debt in/out of a SAFE
              mapping(address => mapping (address => uint256)) public safeRights;
              /**
               * @notice Allow an address to modify your SAFE
               * @param account Account to give SAFE permissions to
               */
              function approveSAFEModification(address account) external {
                  safeRights[msg.sender][account] = 1;
                  emit ApproveSAFEModification(msg.sender, account);
              }
              /**
               * @notice Deny an address the rights to modify your SAFE
               * @param account Account that is denied SAFE permissions
               */
              function denySAFEModification(address account) external {
                  safeRights[msg.sender][account] = 0;
                  emit DenySAFEModification(msg.sender, account);
              }
              /**
              * @notice Checks whether msg.sender has the right to modify a SAFE
              **/
              function canModifySAFE(address safe, address account) public view returns (bool) {
                  return either(safe == account, safeRights[safe][account] == 1);
              }
          
              // --- Data ---
              struct CollateralType {
                  // Total debt issued for this specific collateral type
                  uint256 debtAmount;        // [wad]
                  // Accumulator for interest accrued on this collateral type
                  uint256 accumulatedRate;   // [ray]
                  // Floor price at which a SAFE is allowed to generate debt
                  uint256 safetyPrice;       // [ray]
                  // Maximum amount of debt that can be generated with this collateral type
                  uint256 debtCeiling;       // [rad]
                  // Minimum amount of debt that must be generated by a SAFE using this collateral
                  uint256 debtFloor;         // [rad]
                  // Price at which a SAFE gets liquidated
                  uint256 liquidationPrice;  // [ray]
              }
              struct SAFE {
                  // Total amount of collateral locked in a SAFE
                  uint256 lockedCollateral;  // [wad]
                  // Total amount of debt generated by a SAFE
                  uint256 generatedDebt;     // [wad]
              }
          
              // Data about each collateral type
              mapping (bytes32 => CollateralType)                public collateralTypes;
              // Data about each SAFE
              mapping (bytes32 => mapping (address => SAFE ))    public safes;
              // Balance of each collateral type
              mapping (bytes32 => mapping (address => uint256))  public tokenCollateral;  // [wad]
              // Internal balance of system coins
              mapping (address => uint256)                       public coinBalance;      // [rad]
              // Amount of debt held by an account. Coins & debt are like matter and antimatter. They nullify each other
              mapping (address => uint256)                       public debtBalance;      // [rad]
          
              // Total amount of debt that a single safe can generate
              uint256 public safeDebtCeiling;      // [wad]
              // Total amount of debt (coins) currently issued
              uint256  public globalDebt;          // [rad]
              // 'Bad' debt that's not covered by collateral
              uint256  public globalUnbackedDebt;  // [rad]
              // Maximum amount of debt that can be issued
              uint256  public globalDebtCeiling;   // [rad]
              // Access flag, indicates whether this contract is still active
              uint256  public contractEnabled;
          
              // --- Events ---
              event AddAuthorization(address account);
              event RemoveAuthorization(address account);
              event ApproveSAFEModification(address sender, address account);
              event DenySAFEModification(address sender, address account);
              event InitializeCollateralType(bytes32 collateralType);
              event ModifyParameters(bytes32 parameter, uint256 data);
              event ModifyParameters(bytes32 collateralType, bytes32 parameter, uint256 data);
              event DisableContract();
              event ModifyCollateralBalance(bytes32 indexed collateralType, address indexed account, int256 wad);
              event TransferCollateral(bytes32 indexed collateralType, address indexed src, address indexed dst, uint256 wad);
              event TransferInternalCoins(address indexed src, address indexed dst, uint256 rad);
              event ModifySAFECollateralization(
                  bytes32 indexed collateralType,
                  address indexed safe,
                  address collateralSource,
                  address debtDestination,
                  int256 deltaCollateral,
                  int256 deltaDebt,
                  uint256 lockedCollateral,
                  uint256 generatedDebt,
                  uint256 globalDebt
              );
              event TransferSAFECollateralAndDebt(
                  bytes32 indexed collateralType,
                  address indexed src,
                  address indexed dst,
                  int256 deltaCollateral,
                  int256 deltaDebt,
                  uint256 srcLockedCollateral,
                  uint256 srcGeneratedDebt,
                  uint256 dstLockedCollateral,
                  uint256 dstGeneratedDebt
              );
              event ConfiscateSAFECollateralAndDebt(
                  bytes32 indexed collateralType,
                  address indexed safe,
                  address collateralCounterparty,
                  address debtCounterparty,
                  int256 deltaCollateral,
                  int256 deltaDebt,
                  uint256 globalUnbackedDebt
              );
              event SettleDebt(address indexed account, uint256 rad, uint256 debtBalance, uint256 coinBalance, uint256 globalUnbackedDebt, uint256 globalDebt);
              event CreateUnbackedDebt(
                  address indexed debtDestination,
                  address indexed coinDestination,
                  uint256 rad,
                  uint256 debtDstBalance,
                  uint256 coinDstBalance,
                  uint256 globalUnbackedDebt,
                  uint256 globalDebt
              );
              event UpdateAccumulatedRate(
                  bytes32 indexed collateralType,
                  address surplusDst,
                  int256 rateMultiplier,
                  uint256 dstCoinBalance,
                  uint256 globalDebt
              );
          
              // --- Init ---
              constructor() public {
                  authorizedAccounts[msg.sender] = 1;
                  safeDebtCeiling = uint256(-1);
                  contractEnabled = 1;
                  emit AddAuthorization(msg.sender);
                  emit ModifyParameters("safeDebtCeiling", uint256(-1));
              }
          
              // --- Math ---
              function addition(uint256 x, int256 y) internal pure returns (uint256 z) {
                  z = x + uint256(y);
                  require(y >= 0 || z <= x, "SAFEEngine/add-uint-int-overflow");
                  require(y <= 0 || z >= x, "SAFEEngine/add-uint-int-underflow");
              }
              function addition(int256 x, int256 y) internal pure returns (int256 z) {
                  z = x + y;
                  require(y >= 0 || z <= x, "SAFEEngine/add-int-int-overflow");
                  require(y <= 0 || z >= x, "SAFEEngine/add-int-int-underflow");
              }
              function subtract(uint256 x, int256 y) internal pure returns (uint256 z) {
                  z = x - uint256(y);
                  require(y <= 0 || z <= x, "SAFEEngine/sub-uint-int-overflow");
                  require(y >= 0 || z >= x, "SAFEEngine/sub-uint-int-underflow");
              }
              function subtract(int256 x, int256 y) internal pure returns (int256 z) {
                  z = x - y;
                  require(y <= 0 || z <= x, "SAFEEngine/sub-int-int-overflow");
                  require(y >= 0 || z >= x, "SAFEEngine/sub-int-int-underflow");
              }
              function multiply(uint256 x, int256 y) internal pure returns (int256 z) {
                  z = int256(x) * y;
                  require(int256(x) >= 0, "SAFEEngine/mul-uint-int-null-x");
                  require(y == 0 || z / y == int256(x), "SAFEEngine/mul-uint-int-overflow");
              }
              function addition(uint256 x, uint256 y) internal pure returns (uint256 z) {
                  require((z = x + y) >= x, "SAFEEngine/add-uint-uint-overflow");
              }
              function subtract(uint256 x, uint256 y) internal pure returns (uint256 z) {
                  require((z = x - y) <= x, "SAFEEngine/sub-uint-uint-underflow");
              }
              function multiply(uint256 x, uint256 y) internal pure returns (uint256 z) {
                  require(y == 0 || (z = x * y) / y == x, "SAFEEngine/multiply-uint-uint-overflow");
              }
          
              // --- Administration ---
          
              /**
               * @notice Creates a brand new collateral type
               * @param collateralType Collateral type name (e.g ETH-A, TBTC-B)
               */
              function initializeCollateralType(bytes32 collateralType) external isAuthorized {
                  require(collateralTypes[collateralType].accumulatedRate == 0, "SAFEEngine/collateral-type-already-exists");
                  collateralTypes[collateralType].accumulatedRate = 10 ** 27;
                  emit InitializeCollateralType(collateralType);
              }
              /**
               * @notice Modify general uint256 params
               * @param parameter The name of the parameter modified
               * @param data New value for the parameter
               */
              function modifyParameters(bytes32 parameter, uint256 data) external isAuthorized {
                  require(contractEnabled == 1, "SAFEEngine/contract-not-enabled");
                  if (parameter == "globalDebtCeiling") globalDebtCeiling = data;
                  else if (parameter == "safeDebtCeiling") safeDebtCeiling = data;
                  else revert("SAFEEngine/modify-unrecognized-param");
                  emit ModifyParameters(parameter, data);
              }
              /**
               * @notice Modify collateral specific params
               * @param collateralType Collateral type we modify params for
               * @param parameter The name of the parameter modified
               * @param data New value for the parameter
               */
              function modifyParameters(
                  bytes32 collateralType,
                  bytes32 parameter,
                  uint256 data
              ) external isAuthorized {
                  require(contractEnabled == 1, "SAFEEngine/contract-not-enabled");
                  if (parameter == "safetyPrice") collateralTypes[collateralType].safetyPrice = data;
                  else if (parameter == "liquidationPrice") collateralTypes[collateralType].liquidationPrice = data;
                  else if (parameter == "debtCeiling") collateralTypes[collateralType].debtCeiling = data;
                  else if (parameter == "debtFloor") collateralTypes[collateralType].debtFloor = data;
                  else revert("SAFEEngine/modify-unrecognized-param");
                  emit ModifyParameters(collateralType, parameter, data);
              }
              /**
               * @notice Disable this contract (normally called by GlobalSettlement)
               */
              function disableContract() external isAuthorized {
                  contractEnabled = 0;
                  emit DisableContract();
              }
          
              // --- Fungibility ---
              /**
               * @notice Join/exit collateral into and and out of the system
               * @param collateralType Collateral type we join/exit
               * @param account Account that gets credited/debited
               * @param wad Amount of collateral
               */
              function modifyCollateralBalance(
                  bytes32 collateralType,
                  address account,
                  int256 wad
              ) external isAuthorized {
                  tokenCollateral[collateralType][account] = addition(tokenCollateral[collateralType][account], wad);
                  emit ModifyCollateralBalance(collateralType, account, wad);
              }
              /**
               * @notice Transfer collateral between accounts
               * @param collateralType Collateral type transferred
               * @param src Collateral source
               * @param dst Collateral destination
               * @param wad Amount of collateral transferred
               */
              function transferCollateral(
                  bytes32 collateralType,
                  address src,
                  address dst,
                  uint256 wad
              ) external {
                  require(canModifySAFE(src, msg.sender), "SAFEEngine/not-allowed");
                  tokenCollateral[collateralType][src] = subtract(tokenCollateral[collateralType][src], wad);
                  tokenCollateral[collateralType][dst] = addition(tokenCollateral[collateralType][dst], wad);
                  emit TransferCollateral(collateralType, src, dst, wad);
              }
              /**
               * @notice Transfer internal coins (does not affect external balances from Coin.sol)
               * @param src Coins source
               * @param dst Coins destination
               * @param rad Amount of coins transferred
               */
              function transferInternalCoins(address src, address dst, uint256 rad) external {
                  require(canModifySAFE(src, msg.sender), "SAFEEngine/not-allowed");
                  coinBalance[src] = subtract(coinBalance[src], rad);
                  coinBalance[dst] = addition(coinBalance[dst], rad);
                  emit TransferInternalCoins(src, dst, rad);
              }
          
              function either(bool x, bool y) internal pure returns (bool z) {
                  assembly{ z := or(x, y)}
              }
              function both(bool x, bool y) internal pure returns (bool z) {
                  assembly{ z := and(x, y)}
              }
          
              // --- SAFE Manipulation ---
              /**
               * @notice Add/remove collateral or put back/generate more debt in a SAFE
               * @param collateralType Type of collateral to withdraw/deposit in and from the SAFE
               * @param safe Target SAFE
               * @param collateralSource Account we take collateral from/put collateral into
               * @param debtDestination Account from which we credit/debit coins and debt
               * @param deltaCollateral Amount of collateral added/extract from the SAFE (wad)
               * @param deltaDebt Amount of debt to generate/repay (wad)
               */
              function modifySAFECollateralization(
                  bytes32 collateralType,
                  address safe,
                  address collateralSource,
                  address debtDestination,
                  int256 deltaCollateral,
                  int256 deltaDebt
              ) external {
                  // system is live
                  require(contractEnabled == 1, "SAFEEngine/contract-not-enabled");
          
                  SAFE memory safeData = safes[collateralType][safe];
                  CollateralType memory collateralTypeData = collateralTypes[collateralType];
                  // collateral type has been initialised
                  require(collateralTypeData.accumulatedRate != 0, "SAFEEngine/collateral-type-not-initialized");
          
                  safeData.lockedCollateral      = addition(safeData.lockedCollateral, deltaCollateral);
                  safeData.generatedDebt         = addition(safeData.generatedDebt, deltaDebt);
                  collateralTypeData.debtAmount  = addition(collateralTypeData.debtAmount, deltaDebt);
          
                  int256 deltaAdjustedDebt = multiply(collateralTypeData.accumulatedRate, deltaDebt);
                  uint256 totalDebtIssued  = multiply(collateralTypeData.accumulatedRate, safeData.generatedDebt);
                  globalDebt               = addition(globalDebt, deltaAdjustedDebt);
          
                  // either debt has decreased, or debt ceilings are not exceeded
                  require(
                    either(
                      deltaDebt <= 0,
                      both(multiply(collateralTypeData.debtAmount, collateralTypeData.accumulatedRate) <= collateralTypeData.debtCeiling,
                        globalDebt <= globalDebtCeiling)
                      ),
                    "SAFEEngine/ceiling-exceeded"
                  );
                  // safe is either less risky than before, or it is safe
                  require(
                    either(
                      both(deltaDebt <= 0, deltaCollateral >= 0),
                      totalDebtIssued <= multiply(safeData.lockedCollateral, collateralTypeData.safetyPrice)
                    ),
                    "SAFEEngine/not-safe"
                  );
          
                  // safe is either more safe, or the owner consents
                  require(either(both(deltaDebt <= 0, deltaCollateral >= 0), canModifySAFE(safe, msg.sender)), "SAFEEngine/not-allowed-to-modify-safe");
                  // collateral src consents
                  require(either(deltaCollateral <= 0, canModifySAFE(collateralSource, msg.sender)), "SAFEEngine/not-allowed-collateral-src");
                  // debt dst consents
                  require(either(deltaDebt >= 0, canModifySAFE(debtDestination, msg.sender)), "SAFEEngine/not-allowed-debt-dst");
          
                  // safe has no debt, or a non-dusty amount
                  require(either(safeData.generatedDebt == 0, totalDebtIssued >= collateralTypeData.debtFloor), "SAFEEngine/dust");
          
                  // safe didn't go above the safe debt limit
                  if (deltaDebt > 0) {
                    require(safeData.generatedDebt <= safeDebtCeiling, "SAFEEngine/above-debt-limit");
                  }
          
                  tokenCollateral[collateralType][collateralSource] =
                    subtract(tokenCollateral[collateralType][collateralSource], deltaCollateral);
          
                  coinBalance[debtDestination] = addition(coinBalance[debtDestination], deltaAdjustedDebt);
          
                  safes[collateralType][safe] = safeData;
                  collateralTypes[collateralType] = collateralTypeData;
          
                  emit ModifySAFECollateralization(
                      collateralType,
                      safe,
                      collateralSource,
                      debtDestination,
                      deltaCollateral,
                      deltaDebt,
                      safeData.lockedCollateral,
                      safeData.generatedDebt,
                      globalDebt
                  );
              }
          
              // --- SAFE Fungibility ---
              /**
               * @notice Transfer collateral and/or debt between SAFEs
               * @param collateralType Collateral type transferred between SAFEs
               * @param src Source SAFE
               * @param dst Destination SAFE
               * @param deltaCollateral Amount of collateral to take/add into src and give/take from dst (wad)
               * @param deltaDebt Amount of debt to take/add into src and give/take from dst (wad)
               */
              function transferSAFECollateralAndDebt(
                  bytes32 collateralType,
                  address src,
                  address dst,
                  int256 deltaCollateral,
                  int256 deltaDebt
              ) external {
                  SAFE storage srcSAFE = safes[collateralType][src];
                  SAFE storage dstSAFE = safes[collateralType][dst];
                  CollateralType storage collateralType_ = collateralTypes[collateralType];
          
                  srcSAFE.lockedCollateral = subtract(srcSAFE.lockedCollateral, deltaCollateral);
                  srcSAFE.generatedDebt    = subtract(srcSAFE.generatedDebt, deltaDebt);
                  dstSAFE.lockedCollateral = addition(dstSAFE.lockedCollateral, deltaCollateral);
                  dstSAFE.generatedDebt    = addition(dstSAFE.generatedDebt, deltaDebt);
          
                  uint256 srcTotalDebtIssued = multiply(srcSAFE.generatedDebt, collateralType_.accumulatedRate);
                  uint256 dstTotalDebtIssued = multiply(dstSAFE.generatedDebt, collateralType_.accumulatedRate);
          
                  // both sides consent
                  require(both(canModifySAFE(src, msg.sender), canModifySAFE(dst, msg.sender)), "SAFEEngine/not-allowed");
          
                  // both sides safe
                  require(srcTotalDebtIssued <= multiply(srcSAFE.lockedCollateral, collateralType_.safetyPrice), "SAFEEngine/not-safe-src");
                  require(dstTotalDebtIssued <= multiply(dstSAFE.lockedCollateral, collateralType_.safetyPrice), "SAFEEngine/not-safe-dst");
          
                  // both sides non-dusty
                  require(either(srcTotalDebtIssued >= collateralType_.debtFloor, srcSAFE.generatedDebt == 0), "SAFEEngine/dust-src");
                  require(either(dstTotalDebtIssued >= collateralType_.debtFloor, dstSAFE.generatedDebt == 0), "SAFEEngine/dust-dst");
          
                  emit TransferSAFECollateralAndDebt(
                      collateralType,
                      src,
                      dst,
                      deltaCollateral,
                      deltaDebt,
                      srcSAFE.lockedCollateral,
                      srcSAFE.generatedDebt,
                      dstSAFE.lockedCollateral,
                      dstSAFE.generatedDebt
                  );
              }
          
              // --- SAFE Confiscation ---
              /**
               * @notice Normally used by the LiquidationEngine in order to confiscate collateral and
                 debt from a SAFE and give them to someone else
               * @param collateralType Collateral type the SAFE has locked inside
               * @param safe Target SAFE
               * @param collateralCounterparty Who we take/give collateral to
               * @param debtCounterparty Who we take/give debt to
               * @param deltaCollateral Amount of collateral taken/added into the SAFE (wad)
               * @param deltaDebt Amount of debt taken/added into the SAFE (wad)
               */
              function confiscateSAFECollateralAndDebt(
                  bytes32 collateralType,
                  address safe,
                  address collateralCounterparty,
                  address debtCounterparty,
                  int256 deltaCollateral,
                  int256 deltaDebt
              ) external isAuthorized {
                  SAFE storage safe_ = safes[collateralType][safe];
                  CollateralType storage collateralType_ = collateralTypes[collateralType];
          
                  safe_.lockedCollateral = addition(safe_.lockedCollateral, deltaCollateral);
                  safe_.generatedDebt = addition(safe_.generatedDebt, deltaDebt);
                  collateralType_.debtAmount = addition(collateralType_.debtAmount, deltaDebt);
          
                  int256 deltaTotalIssuedDebt = multiply(collateralType_.accumulatedRate, deltaDebt);
          
                  tokenCollateral[collateralType][collateralCounterparty] = subtract(
                    tokenCollateral[collateralType][collateralCounterparty],
                    deltaCollateral
                  );
                  debtBalance[debtCounterparty] = subtract(
                    debtBalance[debtCounterparty],
                    deltaTotalIssuedDebt
                  );
                  globalUnbackedDebt = subtract(
                    globalUnbackedDebt,
                    deltaTotalIssuedDebt
                  );
          
                  emit ConfiscateSAFECollateralAndDebt(
                      collateralType,
                      safe,
                      collateralCounterparty,
                      debtCounterparty,
                      deltaCollateral,
                      deltaDebt,
                      globalUnbackedDebt
                  );
              }
          
              // --- Settlement ---
              /**
               * @notice Nullify an amount of coins with an equal amount of debt
               * @param rad Amount of debt & coins to destroy
               */
              function settleDebt(uint256 rad) external {
                  address account       = msg.sender;
                  debtBalance[account]  = subtract(debtBalance[account], rad);
                  coinBalance[account]  = subtract(coinBalance[account], rad);
                  globalUnbackedDebt    = subtract(globalUnbackedDebt, rad);
                  globalDebt            = subtract(globalDebt, rad);
                  emit SettleDebt(account, rad, debtBalance[account], coinBalance[account], globalUnbackedDebt, globalDebt);
              }
              /**
               * @notice Usually called by CoinSavingsAccount in order to create unbacked debt
               * @param debtDestination Usually AccountingEngine that can settle debt with surplus
               * @param coinDestination Usually CoinSavingsAccount that passes the new coins to depositors
               * @param rad Amount of debt to create
               */
              function createUnbackedDebt(
                  address debtDestination,
                  address coinDestination,
                  uint256 rad
              ) external isAuthorized {
                  debtBalance[debtDestination]  = addition(debtBalance[debtDestination], rad);
                  coinBalance[coinDestination]  = addition(coinBalance[coinDestination], rad);
                  globalUnbackedDebt            = addition(globalUnbackedDebt, rad);
                  globalDebt                    = addition(globalDebt, rad);
                  emit CreateUnbackedDebt(
                      debtDestination,
                      coinDestination,
                      rad,
                      debtBalance[debtDestination],
                      coinBalance[coinDestination],
                      globalUnbackedDebt,
                      globalDebt
                  );
              }
          
              // --- Rates ---
              /**
               * @notice Usually called by TaxCollector in order to accrue interest on a specific collateral type
               * @param collateralType Collateral type we accrue interest for
               * @param surplusDst Destination for amount of surplus created by applying the interest rate
                 to debt created by SAFEs with 'collateralType'
               * @param rateMultiplier Multiplier applied to the debtAmount in order to calculate the surplus [ray]
               */
              function updateAccumulatedRate(
                  bytes32 collateralType,
                  address surplusDst,
                  int256 rateMultiplier
              ) external isAuthorized {
                  require(contractEnabled == 1, "SAFEEngine/contract-not-enabled");
                  CollateralType storage collateralType_ = collateralTypes[collateralType];
                  collateralType_.accumulatedRate        = addition(collateralType_.accumulatedRate, rateMultiplier);
                  int256 deltaSurplus                    = multiply(collateralType_.debtAmount, rateMultiplier);
                  coinBalance[surplusDst]                = addition(coinBalance[surplusDst], deltaSurplus);
                  globalDebt                             = addition(globalDebt, deltaSurplus);
                  emit UpdateAccumulatedRate(
                      collateralType,
                      surplusDst,
                      rateMultiplier,
                      coinBalance[surplusDst],
                      globalDebt
                  );
              }
          }