Transaction Hash:
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 | ||
---|---|---|---|---|---|
0x22Acb809...77acFE514 |
4.178457801202419911 Eth
Nonce: 1160
|
4.174984945937769425 Eth
Nonce: 1161
| 0.003472855264650486 | ||
0x69c6C08B...b04EFc106 | |||||
0x95222290...5CC4BAfe5
Miner
| (beaverbuild) | 105.839070286198649219 Eth | 105.839174135198649219 Eth | 0.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 )
-
File 1 of 5: GebLenderFirstResortRewardsVested
File 2 of 5: TokenPool
File 3 of 5: UniswapV2Pair
File 4 of 5: AccountingEngine
File 5 of 5: SAFEEngine
/// 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 ); } }