Transaction Hash:
Block:
10526273 at Jul-25-2020 03:35:31 AM +UTC
Transaction Fee:
0.010709634 ETH
$21.89
Gas Used:
137,303 Gas / 78 Gwei
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x4187cC9f...790413bb0 |
0.273265114250050494 Eth
Nonce: 969
|
0.262555480250050494 Eth
Nonce: 970
| 0.010709634 | ||
| 0x762ed657...658C4CA35 | |||||
|
0xD224cA0c...503B79f53
Miner
| (UUPool) | 544.278608051918971398 Eth | 544.289317685918971398 Eth | 0.010709634 |
Execution Trace
0x762ed657b76372f8c08c6f7e0aa4170658c4ca35.c89e4361( )
0xda2d4e1b91fb5c05cfe43ad34ff2e7445668d2b2.7c712387( )-
UniswapV2Pair.STATICCALL( )
MassetProxy.72ea9076( )
Masset.getSwapOutput( _input=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, _output=0xe2f2a5C287993345a840Db3B0845fbC70f5935a5, _quantity=15204121722 ) => ( True, , output=15204121722000000000000 )BasketManagerProxy.2bf596cf( )-
BasketManager.prepareSwapBassets( _input=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, _output=0xe2f2a5C287993345a840Db3B0845fbC70f5935a5, _isMint=True ) => ( True, , [{name:bAsset, type:tuple, order:1, indexed:false, value:[{name:addr, type:address, order:1, indexed:false, value:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, valueString:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48}, {name:status, type:uint8, order:2, indexed:false, value:1, valueString:1}, {name:isTransferFeeCharged, type:bool, order:3, indexed:false, value:false, valueString:False}, {name:ratio, type:uint256, order:4, indexed:false, value:100000000000000000000, valueString:100000000000000000000}, {name:maxWeight, type:uint256, order:5, indexed:false, value:550000000000000000, valueString:550000000000000000}, {name:vaultBalance, type:uint256, order:6, indexed:false, value:12535775768432, valueString:12535775768432}], valueString:[{name:addr, type:address, order:1, indexed:false, value:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, valueString:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48}, {name:status, type:uint8, order:2, indexed:false, value:1, valueString:1}, {name:isTransferFeeCharged, type:bool, order:3, indexed:false, value:false, valueString:False}, {name:ratio, type:uint256, order:4, indexed:false, value:100000000000000000000, valueString:100000000000000000000}, {name:maxWeight, type:uint256, order:5, indexed:false, value:550000000000000000, valueString:550000000000000000}, {name:vaultBalance, type:uint256, order:6, indexed:false, value:12535775768432, valueString:12535775768432}]}, {name:integrator, type:address, order:2, indexed:false, value:0xD55684f4369040C12262949Ff78299f2BC9dB735, valueString:0xD55684f4369040C12262949Ff78299f2BC9dB735}, {name:index, type:uint8, order:3, indexed:false, value:1, valueString:1}], [{name:bAsset, type:tuple, order:1, indexed:false, value:[{name:addr, type:address, order:1, indexed:false, value:0x6B175474E89094C44Da98b954EedeAC495271d0F, valueString:0x6B175474E89094C44Da98b954EedeAC495271d0F}, {name:status, type:uint8, order:2, indexed:false, value:1, valueString:1}, {name:isTransferFeeCharged, type:bool, order:3, indexed:false, value:false, valueString:False}, {name:ratio, type:uint256, order:4, indexed:false, value:100000000, valueString:100000000}, {name:maxWeight, type:uint256, order:5, indexed:false, value:550000000000000000, valueString:550000000000000000}, {name:vaultBalance, type:uint256, order:6, indexed:false, value:152524603790482171917, valueString:152524603790482171917}], valueString:[{name:addr, type:address, order:1, indexed:false, value:0x6B175474E89094C44Da98b954EedeAC495271d0F, valueString:0x6B175474E89094C44Da98b954EedeAC495271d0F}, {name:status, type:uint8, order:2, indexed:false, value:1, valueString:1}, {name:isTransferFeeCharged, type:bool, order:3, indexed:false, value:false, valueString:False}, {name:ratio, type:uint256, order:4, indexed:false, value:100000000, valueString:100000000}, {name:maxWeight, type:uint256, order:5, indexed:false, value:550000000000000000, valueString:550000000000000000}, {name:vaultBalance, type:uint256, order:6, indexed:false, value:152524603790482171917, valueString:152524603790482171917}]}, {name:integrator, type:address, order:2, indexed:false, value:0x0000000000000000000000000000000000000000, valueString:0x0000000000000000000000000000000000000000}, {name:index, type:uint8, order:3, indexed:false, value:0, valueString:0}] )
-
-
ForgeValidator.validateMint( _totalVault=28111948364684910449801226, _bAsset=[{name:addr, type:address, order:1, indexed:false, value:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, valueString:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48}, {name:status, type:uint8, order:2, indexed:false, value:1, valueString:1}, {name:isTransferFeeCharged, type:bool, order:3, indexed:false, value:false, valueString:False}, {name:ratio, type:uint256, order:4, indexed:false, value:100000000000000000000, valueString:100000000000000000000}, {name:maxWeight, type:uint256, order:5, indexed:false, value:550000000000000000, valueString:550000000000000000}, {name:vaultBalance, type:uint256, order:6, indexed:false, value:12535775768432, valueString:12535775768432}], _bAssetQuantity=15204121722 ) => ( isValid=True, reason= )
-
BPool.getBalance( token=0xe2f2a5C287993345a840Db3B0845fbC70f5935a5 ) => ( 4149104168017827086185181 )
-
BPool.getDenormalizedWeight( token=0xe2f2a5C287993345a840Db3B0845fbC70f5935a5 ) => ( 25000000000000000000 )
-
BPool.getBalance( token=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 ) => ( 14702462831130515249680 )
-
BPool.getDenormalizedWeight( token=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 ) => ( 25000000000000000000 )
-
BPool.STATICCALL( )
-
BPool.calcOutGivenIn( tokenBalanceIn=4149104168017827086185181, tokenWeightIn=25000000000000000000, tokenBalanceOut=14702462831130515249680, tokenWeightOut=25000000000000000000, tokenAmountIn=15204121722000000000000, swapFee=1000000000000000 ) => ( tokenAmountOut=53626027724714016493 )
-
File 1 of 7: UniswapV2Pair
File 2 of 7: MassetProxy
File 3 of 7: Masset
File 4 of 7: BasketManagerProxy
File 5 of 7: BasketManager
File 6 of 7: ForgeValidator
File 7 of 7: BPool
// 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 2 of 7: MassetProxy
pragma solidity 0.5.16;
/**
* @title Proxy
* @dev Implements delegation of calls to other contracts, with proper
* forwarding of return values and bubbling of failures.
* It defines a fallback function that delegates all calls to the address
* returned by the abstract _implementation() internal function.
*/
contract Proxy {
/**
* @dev Fallback function.
* Implemented entirely in `_fallback`.
*/
function () payable external {
_fallback();
}
/**
* @return The Address of the implementation.
*/
function _implementation() internal view returns (address);
/**
* @dev Delegates execution to an implementation contract.
* This is a low level function that doesn't return to its internal call site.
* It will return to the external caller whatever the implementation returns.
* @param implementation Address to delegate.
*/
function _delegate(address implementation) internal {
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize)
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas, implementation, 0, calldatasize, 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize)
switch result
// delegatecall returns 0 on error.
case 0 { revert(0, returndatasize) }
default { return(0, returndatasize) }
}
}
/**
* @dev Function that is run as the first thing in the fallback function.
* Can be redefined in derived contracts to add functionality.
* Redefinitions must call super._willFallback().
*/
function _willFallback() internal {
}
/**
* @dev fallback implementation.
* Extracted to enable manual triggering.
*/
function _fallback() internal {
_willFallback();
_delegate(_implementation());
}
}
/**
* Utility library of inline functions on addresses
*
* Source https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-solidity/v2.1.3/contracts/utils/Address.sol
* This contract is copied here and renamed from the original to avoid clashes in the compiled artifacts
* when the user imports a zos-lib contract (that transitively causes this contract to be compiled and added to the
* build/artifacts folder) as well as the vanilla Address implementation from an openzeppelin version.
*/
library OpenZeppelinUpgradesAddress {
/**
* Returns whether the target address is a contract
* @dev This function will return false if invoked during the constructor of a contract,
* as the code is not actually created until after the constructor finishes.
* @param account address of the account to check
* @return whether the target address is a contract
*/
function isContract(address account) internal view returns (bool) {
uint256 size;
// XXX Currently there is no better way to check if there is a contract in an address
// than to check the size of the code at that address.
// See https://ethereum.stackexchange.com/a/14016/36603
// for more details about how this works.
// TODO Check this again before the Serenity release, because all addresses will be
// contracts then.
// solhint-disable-next-line no-inline-assembly
assembly { size := extcodesize(account) }
return size > 0;
}
}
/**
* @title BaseUpgradeabilityProxy
* @dev This contract implements a proxy that allows to change the
* implementation address to which it will delegate.
* Such a change is called an implementation upgrade.
*/
contract BaseUpgradeabilityProxy is Proxy {
/**
* @dev Emitted when the implementation is upgraded.
* @param implementation Address of the new implementation.
*/
event Upgraded(address indexed implementation);
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @dev Returns the current implementation.
* @return Address of the current implementation
*/
function _implementation() internal view returns (address impl) {
bytes32 slot = IMPLEMENTATION_SLOT;
assembly {
impl := sload(slot)
}
}
/**
* @dev Upgrades the proxy to a new implementation.
* @param newImplementation Address of the new implementation.
*/
function _upgradeTo(address newImplementation) internal {
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
}
/**
* @dev Sets the implementation address of the proxy.
* @param newImplementation Address of the new implementation.
*/
function _setImplementation(address newImplementation) internal {
require(OpenZeppelinUpgradesAddress.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address");
bytes32 slot = IMPLEMENTATION_SLOT;
assembly {
sstore(slot, newImplementation)
}
}
}
/**
* @title UpgradeabilityProxy
* @dev Extends BaseUpgradeabilityProxy with a constructor for initializing
* implementation and init data.
*/
contract UpgradeabilityProxy is BaseUpgradeabilityProxy {
/**
* @dev Contract constructor.
* @param _logic Address of the initial implementation.
* @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
* It should include the signature and the parameters of the function to be called, as described in
* https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
* This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
*/
constructor(address _logic, bytes memory _data) public payable {
assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1));
_setImplementation(_logic);
if(_data.length > 0) {
(bool success,) = _logic.delegatecall(_data);
require(success);
}
}
}
/**
* @title BaseAdminUpgradeabilityProxy
* @dev This contract combines an upgradeability proxy with an authorization
* mechanism for administrative tasks.
* All external functions in this contract must be guarded by the
* `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity
* feature proposal that would enable this to be done automatically.
*/
contract BaseAdminUpgradeabilityProxy is BaseUpgradeabilityProxy {
/**
* @dev Emitted when the administration has been transferred.
* @param previousAdmin Address of the previous admin.
* @param newAdmin Address of the new admin.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Modifier to check whether the `msg.sender` is the admin.
* If it is, it will run the function. Otherwise, it will delegate the call
* to the implementation.
*/
modifier ifAdmin() {
if (msg.sender == _admin()) {
_;
} else {
_fallback();
}
}
/**
* @return The address of the proxy admin.
*/
function admin() external ifAdmin returns (address) {
return _admin();
}
/**
* @return The address of the implementation.
*/
function implementation() external ifAdmin returns (address) {
return _implementation();
}
/**
* @dev Changes the admin of the proxy.
* Only the current admin can call this function.
* @param newAdmin Address to transfer proxy administration to.
*/
function changeAdmin(address newAdmin) external ifAdmin {
require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address");
emit AdminChanged(_admin(), newAdmin);
_setAdmin(newAdmin);
}
/**
* @dev Upgrade the backing implementation of the proxy.
* Only the admin can call this function.
* @param newImplementation Address of the new implementation.
*/
function upgradeTo(address newImplementation) external ifAdmin {
_upgradeTo(newImplementation);
}
/**
* @dev Upgrade the backing implementation of the proxy and call a function
* on the new implementation.
* This is useful to initialize the proxied contract.
* @param newImplementation Address of the new implementation.
* @param data Data to send as msg.data in the low level call.
* It should include the signature and the parameters of the function to be called, as described in
* https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
*/
function upgradeToAndCall(address newImplementation, bytes calldata data) payable external ifAdmin {
_upgradeTo(newImplementation);
(bool success,) = newImplementation.delegatecall(data);
require(success);
}
/**
* @return The admin slot.
*/
function _admin() internal view returns (address adm) {
bytes32 slot = ADMIN_SLOT;
assembly {
adm := sload(slot)
}
}
/**
* @dev Sets the address of the proxy admin.
* @param newAdmin Address of the new proxy admin.
*/
function _setAdmin(address newAdmin) internal {
bytes32 slot = ADMIN_SLOT;
assembly {
sstore(slot, newAdmin)
}
}
/**
* @dev Only fall back when the sender is not the admin.
*/
function _willFallback() internal {
require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin");
super._willFallback();
}
}
/**
* @title InitializableUpgradeabilityProxy
* @dev Extends BaseUpgradeabilityProxy with an initializer for initializing
* implementation and init data.
*/
contract InitializableUpgradeabilityProxy is BaseUpgradeabilityProxy {
/**
* @dev Contract initializer.
* @param _logic Address of the initial implementation.
* @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
* It should include the signature and the parameters of the function to be called, as described in
* https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
* This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
*/
function initialize(address _logic, bytes memory _data) public payable {
require(_implementation() == address(0));
assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1));
_setImplementation(_logic);
if(_data.length > 0) {
(bool success,) = _logic.delegatecall(_data);
require(success);
}
}
}
contract InitializableAdminUpgradeabilityProxy is BaseAdminUpgradeabilityProxy, InitializableUpgradeabilityProxy {
/**
* Contract initializer.
* @param _logic address of the initial implementation.
* @param _admin Address of the proxy administrator.
* @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
* It should include the signature and the parameters of the function to be called, as described in
* https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
* This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
*/
function initialize(address _logic, address _admin, bytes memory _data) public payable {
require(_implementation() == address(0));
InitializableUpgradeabilityProxy.initialize(_logic, _data);
assert(ADMIN_SLOT == bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1));
_setAdmin(_admin);
}
}
/**
* @notice MassetProxy delegates calls to a Masset implementation
* @dev Extending on OpenZeppelin's InitializableAdminUpgradabilityProxy
* means that the proxy is upgradable through a ProxyAdmin. MassetProxy upgrades
* are implemented by a DelayedProxyAdmin, which enforces a 1 week opt-out period.
* All upgrades are governed through the current mStable governance.
*/
contract MassetProxy is InitializableAdminUpgradeabilityProxy {
}File 3 of 7: Masset
pragma solidity 0.5.16;
pragma experimental ABIEncoderV2;
interface MassetStructs {
/** @dev Stores high level basket info */
struct Basket {
/** @dev Array of Bassets currently active */
Basset[] bassets;
/** @dev Max number of bAssets that can be present in any Basket */
uint8 maxBassets;
/** @dev Some bAsset is undergoing re-collateralisation */
bool undergoingRecol;
/**
* @dev In the event that we do not raise enough funds from the auctioning of a failed Basset,
* The Basket is deemed as failed, and is undercollateralised to a certain degree.
* The collateralisation ratio is used to calc Masset burn rate.
*/
bool failed;
uint256 collateralisationRatio;
}
/** @dev Stores bAsset info. The struct takes 5 storage slots per Basset */
struct Basset {
/** @dev Address of the bAsset */
address addr;
/** @dev Status of the basset, */
BassetStatus status; // takes uint8 datatype (1 byte) in storage
/** @dev An ERC20 can charge transfer fee, for example USDT, DGX tokens. */
bool isTransferFeeCharged; // takes a byte in storage
/**
* @dev 1 Basset * ratio / ratioScale == x Masset (relative value)
* If ratio == 10e8 then 1 bAsset = 10 mAssets
* A ratio is divised as 10^(18-tokenDecimals) * measurementMultiple(relative value of 1 base unit)
*/
uint256 ratio;
/** @dev Target weights of the Basset (100% == 1e18) */
uint256 maxWeight;
/** @dev Amount of the Basset that is held in Collateral */
uint256 vaultBalance;
}
/** @dev Status of the Basset - has it broken its peg? */
enum BassetStatus {
Default,
Normal,
BrokenBelowPeg,
BrokenAbovePeg,
Blacklisted,
Liquidating,
Liquidated,
Failed
}
/** @dev Internal details on Basset */
struct BassetDetails {
Basset bAsset;
address integrator;
uint8 index;
}
/** @dev All details needed to Forge with multiple bAssets */
struct ForgePropsMulti {
bool isValid; // Flag to signify that forge bAssets have passed validity check
Basset[] bAssets;
address[] integrators;
uint8[] indexes;
}
/** @dev All details needed for proportionate Redemption */
struct RedeemPropsMulti {
uint256 colRatio;
Basset[] bAssets;
address[] integrators;
uint8[] indexes;
}
}
contract IForgeValidator is MassetStructs {
function validateMint(uint256 _totalVault, Basset calldata _basset, uint256 _bAssetQuantity)
external pure returns (bool, string memory);
function validateMintMulti(uint256 _totalVault, Basset[] calldata _bassets, uint256[] calldata _bAssetQuantities)
external pure returns (bool, string memory);
function validateSwap(uint256 _totalVault, Basset calldata _inputBasset, Basset calldata _outputBasset, uint256 _quantity)
external pure returns (bool, string memory, uint256, bool);
function validateRedemption(
bool basketIsFailed,
uint256 _totalVault,
Basset[] calldata _allBassets,
uint8[] calldata _indices,
uint256[] calldata _bassetQuantities) external pure returns (bool, string memory, bool);
function calculateRedemptionMulti(
uint256 _mAssetQuantity,
Basset[] calldata _allBassets) external pure returns (bool, string memory, uint256[] memory);
}
interface IPlatformIntegration {
/**
* @dev Deposit the given bAsset to Lending platform
* @param _bAsset bAsset address
* @param _amount Amount to deposit
*/
function deposit(address _bAsset, uint256 _amount, bool isTokenFeeCharged)
external returns (uint256 quantityDeposited);
/**
* @dev Withdraw given bAsset from Lending platform
*/
function withdraw(address _receiver, address _bAsset, uint256 _amount, bool _isTokenFeeCharged) external;
/**
* @dev Returns the current balance of the given bAsset
*/
function checkBalance(address _bAsset) external returns (uint256 balance);
}
contract IBasketManager is MassetStructs {
/** @dev Setters for mAsset to update balances */
function increaseVaultBalance(
uint8 _bAsset,
address _integrator,
uint256 _increaseAmount) external;
function increaseVaultBalances(
uint8[] calldata _bAsset,
address[] calldata _integrator,
uint256[] calldata _increaseAmount) external;
function decreaseVaultBalance(
uint8 _bAsset,
address _integrator,
uint256 _decreaseAmount) external;
function decreaseVaultBalances(
uint8[] calldata _bAsset,
address[] calldata _integrator,
uint256[] calldata _decreaseAmount) external;
function collectInterest() external
returns (uint256 interestCollected, uint256[] memory gains);
/** @dev Setters for Gov to update Basket composition */
function addBasset(
address _basset,
address _integration,
bool _isTransferFeeCharged) external returns (uint8 index);
function setBasketWeights(address[] calldata _bassets, uint256[] calldata _weights) external;
function setTransferFeesFlag(address _bAsset, bool _flag) external;
/** @dev Getters to retrieve Basket information */
function getBasket() external view returns (Basket memory b);
function prepareForgeBasset(address _token, uint256 _amt, bool _mint) external
returns (bool isValid, BassetDetails memory bInfo);
function prepareSwapBassets(address _input, address _output, bool _isMint) external view
returns (bool, string memory, BassetDetails memory, BassetDetails memory);
function prepareForgeBassets(address[] calldata _bAssets, uint256[] calldata _amts, bool _mint) external
returns (ForgePropsMulti memory props);
function prepareRedeemMulti() external view
returns (RedeemPropsMulti memory props);
function getBasset(address _token) external view
returns (Basset memory bAsset);
function getBassets() external view
returns (Basset[] memory bAssets, uint256 len);
/** @dev Recollateralisation */
function handlePegLoss(address _basset, bool _belowPeg) external returns (bool actioned);
function negateIsolation(address _basset) external;
}
contract IMasset is MassetStructs {
/** @dev Calc interest */
function collectInterest() external returns (uint256 massetMinted, uint256 newTotalSupply);
/** @dev Minting */
function mint(address _basset, uint256 _bassetQuantity)
external returns (uint256 massetMinted);
function mintTo(address _basset, uint256 _bassetQuantity, address _recipient)
external returns (uint256 massetMinted);
function mintMulti(address[] calldata _bAssets, uint256[] calldata _bassetQuantity, address _recipient)
external returns (uint256 massetMinted);
/** @dev Swapping */
function swap( address _input, address _output, uint256 _quantity, address _recipient)
external returns (uint256 output);
function getSwapOutput( address _input, address _output, uint256 _quantity)
external view returns (bool, string memory, uint256 output);
/** @dev Redeeming */
function redeem(address _basset, uint256 _bassetQuantity)
external returns (uint256 massetRedeemed);
function redeemTo(address _basset, uint256 _bassetQuantity, address _recipient)
external returns (uint256 massetRedeemed);
function redeemMulti(address[] calldata _bAssets, uint256[] calldata _bassetQuantities, address _recipient)
external returns (uint256 massetRedeemed);
function redeemMasset(uint256 _mAssetQuantity, address _recipient) external;
/** @dev Setters for the Manager or Gov to update module info */
function upgradeForgeValidator(address _newForgeValidator) external;
/** @dev Setters for Gov to set system params */
function setSwapFee(uint256 _swapFee) external;
/** @dev Getters */
function getBasketManager() external view returns(address);
}
contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
*/
bool private initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private initializing;
/**
* @dev Modifier to use in the initializer function of a contract.
*/
modifier initializer() {
require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized");
bool isTopLevelCall = !initializing;
if (isTopLevelCall) {
initializing = true;
initialized = true;
}
_;
if (isTopLevelCall) {
initializing = false;
}
}
/// @dev Returns true if and only if the function is running in the constructor
function isConstructor() private view returns (bool) {
// extcodesize checks the size of the code stored in an address, and
// address returns the current address. Since the code is still not
// deployed when running a constructor, any checks on its code size will
// yield zero, making it an effective way to detect if a contract is
// under construction or not.
address self = address(this);
uint256 cs;
assembly { cs := extcodesize(self) }
return cs == 0;
}
// Reserved storage space to allow for layout changes in the future.
uint256[50] private ______gap;
}
/*
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
contract Context {
// Empty internal constructor, to prevent people from mistakenly deploying
// an instance of this contract, which should be used via inheritance.
constructor () internal { }
// solhint-disable-previous-line no-empty-blocks
function _msgSender() internal view returns (address payable) {
return msg.sender;
}
function _msgData() internal view returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}
/**
* @dev Interface of the ERC20 standard as defined in the EIP. Does not include
* the optional functions; to access them see {ERC20Detailed}.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*
* _Available since v2.4.0._
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*
* _Available since v2.4.0._
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*
* _Available since v2.4.0._
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
contract ERC20 is Context, IERC20 {
using SafeMath for uint256;
mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) private _allowances;
uint256 private _totalSupply;
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `recipient` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address recipient, uint256 amount) public returns (bool) {
_transfer(_msgSender(), recipient, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount) public returns (bool) {
_approve(_msgSender(), spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20};
*
* Requirements:
* - `sender` and `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
* - the caller must have allowance for `sender`'s tokens of at least
* `amount`.
*/
function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
_transfer(sender, recipient, amount);
_approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
return true;
}
/**
* @dev Moves tokens `amount` from `sender` to `recipient`.
*
* This is internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `sender` cannot be the zero address.
* - `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
*/
function _transfer(address sender, address recipient, uint256 amount) internal {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
_balances[recipient] = _balances[recipient].add(amount);
emit Transfer(sender, recipient, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements
*
* - `to` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal {
require(account != address(0), "ERC20: mint to the zero address");
_totalSupply = _totalSupply.add(amount);
_balances[account] = _balances[account].add(amount);
emit Transfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal {
require(account != address(0), "ERC20: burn from the zero address");
_balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
_totalSupply = _totalSupply.sub(amount);
emit Transfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
*
* This is internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(address owner, address spender, uint256 amount) internal {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Destroys `amount` tokens from `account`.`amount` is then deducted
* from the caller's allowance.
*
* See {_burn} and {_approve}.
*/
function _burnFrom(address account, uint256 amount) internal {
_burn(account, amount);
_approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance"));
}
}
contract InitializableERC20Detailed is IERC20 {
string private _name;
string private _symbol;
uint8 private _decimals;
/**
* @dev Sets the values for `name`, `symbol`, and `decimals`. All three of
* these values are immutable: they can only be set once during
* construction.
* @notice To avoid variable shadowing appended `Arg` after arguments name.
*/
function _initialize(string memory nameArg, string memory symbolArg, uint8 decimalsArg) internal {
_name = nameArg;
_symbol = symbolArg;
_decimals = decimalsArg;
}
/**
* @dev Returns the name of the token.
*/
function name() public view returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5,05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view returns (uint8) {
return _decimals;
}
}
contract InitializableToken is ERC20, InitializableERC20Detailed {
/**
* @dev Initialization function for implementing contract
* @notice To avoid variable shadowing appended `Arg` after arguments name.
*/
function _initialize(string memory _nameArg, string memory _symbolArg) internal {
InitializableERC20Detailed._initialize(_nameArg, _symbolArg, 18);
}
}
contract InitializableModuleKeys {
// Governance // Phases
bytes32 internal KEY_GOVERNANCE; // 2.x
bytes32 internal KEY_STAKING; // 1.2
bytes32 internal KEY_PROXY_ADMIN; // 1.0
// mStable
bytes32 internal KEY_ORACLE_HUB; // 1.2
bytes32 internal KEY_MANAGER; // 1.2
bytes32 internal KEY_RECOLLATERALISER; // 2.x
bytes32 internal KEY_META_TOKEN; // 1.1
bytes32 internal KEY_SAVINGS_MANAGER; // 1.0
/**
* @dev Initialize function for upgradable proxy contracts. This function should be called
* via Proxy to initialize constants in the Proxy contract.
*/
function _initialize() internal {
// keccak256() values are evaluated only once at the time of this function call.
// Hence, no need to assign hard-coded values to these variables.
KEY_GOVERNANCE = keccak256("Governance");
KEY_STAKING = keccak256("Staking");
KEY_PROXY_ADMIN = keccak256("ProxyAdmin");
KEY_ORACLE_HUB = keccak256("OracleHub");
KEY_MANAGER = keccak256("Manager");
KEY_RECOLLATERALISER = keccak256("Recollateraliser");
KEY_META_TOKEN = keccak256("MetaToken");
KEY_SAVINGS_MANAGER = keccak256("SavingsManager");
}
}
interface INexus {
function governor() external view returns (address);
function getModule(bytes32 key) external view returns (address);
function proposeModule(bytes32 _key, address _addr) external;
function cancelProposedModule(bytes32 _key) external;
function acceptProposedModule(bytes32 _key) external;
function acceptProposedModules(bytes32[] calldata _keys) external;
function requestLockModule(bytes32 _key) external;
function cancelLockModule(bytes32 _key) external;
function lockModule(bytes32 _key) external;
}
contract InitializableModule is InitializableModuleKeys {
INexus public nexus;
/**
* @dev Modifier to allow function calls only from the Governor.
*/
modifier onlyGovernor() {
require(msg.sender == _governor(), "Only governor can execute");
_;
}
/**
* @dev Modifier to allow function calls only from the Governance.
* Governance is either Governor address or Governance address.
*/
modifier onlyGovernance() {
require(
msg.sender == _governor() || msg.sender == _governance(),
"Only governance can execute"
);
_;
}
/**
* @dev Modifier to allow function calls only from the ProxyAdmin.
*/
modifier onlyProxyAdmin() {
require(
msg.sender == _proxyAdmin(), "Only ProxyAdmin can execute"
);
_;
}
/**
* @dev Modifier to allow function calls only from the Manager.
*/
modifier onlyManager() {
require(msg.sender == _manager(), "Only manager can execute");
_;
}
/**
* @dev Initialization function for upgradable proxy contracts
* @param _nexus Nexus contract address
*/
function _initialize(address _nexus) internal {
require(_nexus != address(0), "Nexus address is zero");
nexus = INexus(_nexus);
InitializableModuleKeys._initialize();
}
/**
* @dev Returns Governor address from the Nexus
* @return Address of Governor Contract
*/
function _governor() internal view returns (address) {
return nexus.governor();
}
/**
* @dev Returns Governance Module address from the Nexus
* @return Address of the Governance (Phase 2)
*/
function _governance() internal view returns (address) {
return nexus.getModule(KEY_GOVERNANCE);
}
/**
* @dev Return Staking Module address from the Nexus
* @return Address of the Staking Module contract
*/
function _staking() internal view returns (address) {
return nexus.getModule(KEY_STAKING);
}
/**
* @dev Return ProxyAdmin Module address from the Nexus
* @return Address of the ProxyAdmin Module contract
*/
function _proxyAdmin() internal view returns (address) {
return nexus.getModule(KEY_PROXY_ADMIN);
}
/**
* @dev Return MetaToken Module address from the Nexus
* @return Address of the MetaToken Module contract
*/
function _metaToken() internal view returns (address) {
return nexus.getModule(KEY_META_TOKEN);
}
/**
* @dev Return OracleHub Module address from the Nexus
* @return Address of the OracleHub Module contract
*/
function _oracleHub() internal view returns (address) {
return nexus.getModule(KEY_ORACLE_HUB);
}
/**
* @dev Return Manager Module address from the Nexus
* @return Address of the Manager Module contract
*/
function _manager() internal view returns (address) {
return nexus.getModule(KEY_MANAGER);
}
/**
* @dev Return SavingsManager Module address from the Nexus
* @return Address of the SavingsManager Module contract
*/
function _savingsManager() internal view returns (address) {
return nexus.getModule(KEY_SAVINGS_MANAGER);
}
/**
* @dev Return Recollateraliser Module address from the Nexus
* @return Address of the Recollateraliser Module contract (Phase 2)
*/
function _recollateraliser() internal view returns (address) {
return nexus.getModule(KEY_RECOLLATERALISER);
}
}
contract InitializableReentrancyGuard {
bool private _notEntered;
function _initialize() internal {
// Storing an initial 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 percetange 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.
_notEntered = true;
}
/**
* @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(_notEntered, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_notEntered = false;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_notEntered = true;
}
}
library StableMath {
using SafeMath for uint256;
/**
* @dev Scaling unit for use in specific calculations,
* where 1 * 10**18, or 1e18 represents a unit '1'
*/
uint256 private constant FULL_SCALE = 1e18;
/**
* @notice Token Ratios are used when converting between units of bAsset, mAsset and MTA
* Reasoning: Takes into account token decimals, and difference in base unit (i.e. grams to Troy oz for gold)
* @dev bAsset ratio unit for use in exact calculations,
* where (1 bAsset unit * bAsset.ratio) / ratioScale == x mAsset unit
*/
uint256 private constant RATIO_SCALE = 1e8;
/**
* @dev Provides an interface to the scaling unit
* @return Scaling unit (1e18 or 1 * 10**18)
*/
function getFullScale() internal pure returns (uint256) {
return FULL_SCALE;
}
/**
* @dev Provides an interface to the ratio unit
* @return Ratio scale unit (1e8 or 1 * 10**8)
*/
function getRatioScale() internal pure returns (uint256) {
return RATIO_SCALE;
}
/**
* @dev Scales a given integer to the power of the full scale.
* @param x Simple uint256 to scale
* @return Scaled value a to an exact number
*/
function scaleInteger(uint256 x)
internal
pure
returns (uint256)
{
return x.mul(FULL_SCALE);
}
/***************************************
PRECISE ARITHMETIC
****************************************/
/**
* @dev Multiplies two precise units, and then truncates by the full scale
* @param x Left hand input to multiplication
* @param y Right hand input to multiplication
* @return Result after multiplying the two inputs and then dividing by the shared
* scale unit
*/
function mulTruncate(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
return mulTruncateScale(x, y, FULL_SCALE);
}
/**
* @dev Multiplies two precise units, and then truncates by the given scale. For example,
* when calculating 90% of 10e18, (10e18 * 9e17) / 1e18 = (9e36) / 1e18 = 9e18
* @param x Left hand input to multiplication
* @param y Right hand input to multiplication
* @param scale Scale unit
* @return Result after multiplying the two inputs and then dividing by the shared
* scale unit
*/
function mulTruncateScale(uint256 x, uint256 y, uint256 scale)
internal
pure
returns (uint256)
{
// e.g. assume scale = fullScale
// z = 10e18 * 9e17 = 9e36
uint256 z = x.mul(y);
// return 9e38 / 1e18 = 9e18
return z.div(scale);
}
/**
* @dev Multiplies two precise units, and then truncates by the full scale, rounding up the result
* @param x Left hand input to multiplication
* @param y Right hand input to multiplication
* @return Result after multiplying the two inputs and then dividing by the shared
* scale unit, rounded up to the closest base unit.
*/
function mulTruncateCeil(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
// e.g. 8e17 * 17268172638 = 138145381104e17
uint256 scaled = x.mul(y);
// e.g. 138145381104e17 + 9.99...e17 = 138145381113.99...e17
uint256 ceil = scaled.add(FULL_SCALE.sub(1));
// e.g. 13814538111.399...e18 / 1e18 = 13814538111
return ceil.div(FULL_SCALE);
}
/**
* @dev Precisely divides two units, by first scaling the left hand operand. Useful
* for finding percentage weightings, i.e. 8e18/10e18 = 80% (or 8e17)
* @param x Left hand input to division
* @param y Right hand input to division
* @return Result after multiplying the left operand by the scale, and
* executing the division on the right hand input.
*/
function divPrecisely(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
// e.g. 8e18 * 1e18 = 8e36
uint256 z = x.mul(FULL_SCALE);
// e.g. 8e36 / 10e18 = 8e17
return z.div(y);
}
/***************************************
RATIO FUNCS
****************************************/
/**
* @dev Multiplies and truncates a token ratio, essentially flooring the result
* i.e. How much mAsset is this bAsset worth?
* @param x Left hand operand to multiplication (i.e Exact quantity)
* @param ratio bAsset ratio
* @return Result after multiplying the two inputs and then dividing by the ratio scale
*/
function mulRatioTruncate(uint256 x, uint256 ratio)
internal
pure
returns (uint256 c)
{
return mulTruncateScale(x, ratio, RATIO_SCALE);
}
/**
* @dev Multiplies and truncates a token ratio, rounding up the result
* i.e. How much mAsset is this bAsset worth?
* @param x Left hand input to multiplication (i.e Exact quantity)
* @param ratio bAsset ratio
* @return Result after multiplying the two inputs and then dividing by the shared
* ratio scale, rounded up to the closest base unit.
*/
function mulRatioTruncateCeil(uint256 x, uint256 ratio)
internal
pure
returns (uint256)
{
// e.g. How much mAsset should I burn for this bAsset (x)?
// 1e18 * 1e8 = 1e26
uint256 scaled = x.mul(ratio);
// 1e26 + 9.99e7 = 100..00.999e8
uint256 ceil = scaled.add(RATIO_SCALE.sub(1));
// return 100..00.999e8 / 1e8 = 1e18
return ceil.div(RATIO_SCALE);
}
/**
* @dev Precisely divides two ratioed units, by first scaling the left hand operand
* i.e. How much bAsset is this mAsset worth?
* @param x Left hand operand in division
* @param ratio bAsset ratio
* @return Result after multiplying the left operand by the scale, and
* executing the division on the right hand input.
*/
function divRatioPrecisely(uint256 x, uint256 ratio)
internal
pure
returns (uint256 c)
{
// e.g. 1e14 * 1e8 = 1e22
uint256 y = x.mul(RATIO_SCALE);
// return 1e22 / 1e12 = 1e10
return y.div(ratio);
}
/***************************************
HELPERS
****************************************/
/**
* @dev Calculates minimum of two numbers
* @param x Left hand input
* @param y Right hand input
* @return Minimum of the two inputs
*/
function min(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
return x > y ? y : x;
}
/**
* @dev Calculated maximum of two numbers
* @param x Left hand input
* @param y Right hand input
* @return Maximum of the two inputs
*/
function max(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
return x > y ? x : y;
}
/**
* @dev Clamps a value to an upper bound
* @param x Left hand input
* @param upperBound Maximum possible value to return
* @return Input x clamped to a maximum value, upperBound
*/
function clamp(uint256 x, uint256 upperBound)
internal
pure
returns (uint256)
{
return x > upperBound ? upperBound : x;
}
}
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
// for accounts without code, i.e. `keccak256('')`
bytes32 codehash;
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
// solhint-disable-next-line no-inline-assembly
assembly { codehash := extcodehash(account) }
return (codehash != accountHash && codehash != 0x0);
}
/**
* @dev Converts an `address` into `address payable`. Note that this is
* simply a type cast: the actual underlying value is not changed.
*
* _Available since v2.4.0._
*/
function toPayable(address account) internal pure returns (address payable) {
return address(uint160(account));
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*
* _Available since v2.4.0._
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
// solhint-disable-next-line avoid-call-value
(bool success, ) = recipient.call.value(amount)("");
require(success, "Address: unable to send value, recipient may have reverted");
}
}
library SafeERC20 {
using SafeMath for uint256;
using Address for address;
function safeTransfer(IERC20 token, address to, uint256 value) internal {
callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require((value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).add(value);
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves.
// A Solidity high level call has three parts:
// 1. The target address is checked to verify it contains contract code
// 2. The call itself is made, and success asserted
// 3. The return value is decoded, which in turn checks the size of the returned data.
// solhint-disable-next-line max-line-length
require(address(token).isContract(), "SafeERC20: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = address(token).call(data);
require(success, "SafeERC20: low-level call failed");
if (returndata.length > 0) { // Return data is optional
// solhint-disable-next-line max-line-length
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
library MassetHelpers {
using StableMath for uint256;
using SafeMath for uint256;
using SafeERC20 for IERC20;
function transferTokens(
address _sender,
address _recipient,
address _basset,
bool _erc20TransferFeeCharged,
uint256 _qty
)
internal
returns (uint256 receivedQty)
{
receivedQty = _qty;
if(_erc20TransferFeeCharged) {
uint256 balBefore = IERC20(_basset).balanceOf(_recipient);
IERC20(_basset).safeTransferFrom(_sender, _recipient, _qty);
uint256 balAfter = IERC20(_basset).balanceOf(_recipient);
receivedQty = StableMath.min(_qty, balAfter.sub(balBefore));
} else {
IERC20(_basset).safeTransferFrom(_sender, _recipient, _qty);
}
}
function safeInfiniteApprove(address _asset, address _spender)
internal
{
IERC20(_asset).safeApprove(_spender, 0);
IERC20(_asset).safeApprove(_spender, uint256(-1));
}
}
/**
* @title Masset
* @author Stability Labs Pty. Ltd.
* @notice The Masset is a token that allows minting and redemption at a 1:1 ratio
* for underlying basket assets (bAssets) of the same peg (i.e. USD,
* EUR, Gold). Composition and validation is enforced via the BasketManager.
* @dev VERSION: 1.1
* DATE: 2020-06-30
*/
contract Masset is
Initializable,
IMasset,
InitializableToken,
InitializableModule,
InitializableReentrancyGuard
{
using StableMath for uint256;
// Forging Events
event Minted(address indexed minter, address recipient, uint256 mAssetQuantity, address bAsset, uint256 bAssetQuantity);
event MintedMulti(address indexed minter, address recipient, uint256 mAssetQuantity, address[] bAssets, uint256[] bAssetQuantities);
event Swapped(address indexed swapper, address input, address output, uint256 outputAmount, address recipient);
event Redeemed(address indexed redeemer, address recipient, uint256 mAssetQuantity, address[] bAssets, uint256[] bAssetQuantities);
event RedeemedMasset(address indexed redeemer, address recipient, uint256 mAssetQuantity);
event PaidFee(address indexed payer, address asset, uint256 feeQuantity);
// State Events
event SwapFeeChanged(uint256 fee);
event RedemptionFeeChanged(uint256 fee);
event ForgeValidatorChanged(address forgeValidator);
// Modules and connectors
IForgeValidator public forgeValidator;
bool private forgeValidatorLocked;
IBasketManager private basketManager;
// Basic redemption fee information
uint256 public swapFee;
uint256 private MAX_FEE;
/**
* @dev Constructor
* @notice To avoid variable shadowing appended `Arg` after arguments name.
*/
function initialize(
string calldata _nameArg,
string calldata _symbolArg,
address _nexus,
address _forgeValidator,
address _basketManager
)
external
initializer
{
InitializableToken._initialize(_nameArg, _symbolArg);
InitializableModule._initialize(_nexus);
InitializableReentrancyGuard._initialize();
forgeValidator = IForgeValidator(_forgeValidator);
basketManager = IBasketManager(_basketManager);
MAX_FEE = 2e16;
swapFee = 4e15;
}
/**
* @dev Verifies that the caller is the Savings Manager contract
*/
modifier onlySavingsManager() {
require(_savingsManager() == msg.sender, "Must be savings manager");
_;
}
/***************************************
MINTING (PUBLIC)
****************************************/
/**
* @dev Mint a single bAsset, at a 1:1 ratio with the bAsset. This contract
* must have approval to spend the senders bAsset
* @param _bAsset Address of the bAsset to mint
* @param _bAssetQuantity Quantity in bAsset units
* @return massetMinted Number of newly minted mAssets
*/
function mint(
address _bAsset,
uint256 _bAssetQuantity
)
external
nonReentrant
returns (uint256 massetMinted)
{
return _mintTo(_bAsset, _bAssetQuantity, msg.sender);
}
/**
* @dev Mint a single bAsset, at a 1:1 ratio with the bAsset. This contract
* must have approval to spend the senders bAsset
* @param _bAsset Address of the bAsset to mint
* @param _bAssetQuantity Quantity in bAsset units
* @param _recipient receipient of the newly minted mAsset tokens
* @return massetMinted Number of newly minted mAssets
*/
function mintTo(
address _bAsset,
uint256 _bAssetQuantity,
address _recipient
)
external
nonReentrant
returns (uint256 massetMinted)
{
return _mintTo(_bAsset, _bAssetQuantity, _recipient);
}
/**
* @dev Mint with multiple bAssets, at a 1:1 ratio to mAsset. This contract
* must have approval to spend the senders bAssets
* @param _bAssets Non-duplicate address array of bAssets with which to mint
* @param _bAssetQuantity Quantity of each bAsset to mint. Order of array
* should mirror the above
* @param _recipient Address to receive the newly minted mAsset tokens
* @return massetMinted Number of newly minted mAssets
*/
function mintMulti(
address[] calldata _bAssets,
uint256[] calldata _bAssetQuantity,
address _recipient
)
external
nonReentrant
returns(uint256 massetMinted)
{
return _mintTo(_bAssets, _bAssetQuantity, _recipient);
}
/***************************************
MINTING (INTERNAL)
****************************************/
/** @dev Mint Single */
function _mintTo(
address _bAsset,
uint256 _bAssetQuantity,
address _recipient
)
internal
returns (uint256 massetMinted)
{
require(_recipient != address(0), "Must be a valid recipient");
require(_bAssetQuantity > 0, "Quantity must not be 0");
(bool isValid, BassetDetails memory bInfo) = basketManager.prepareForgeBasset(_bAsset, _bAssetQuantity, true);
if(!isValid) return 0;
// Transfer collateral to the platform integration address and call deposit
address integrator = bInfo.integrator;
(uint256 quantityDeposited, uint256 ratioedDeposit) =
_depositTokens(_bAsset, bInfo.bAsset.ratio, integrator, bInfo.bAsset.isTransferFeeCharged, _bAssetQuantity);
// Validation should be after token transfer, as bAssetQty is unknown before
(bool mintValid, string memory reason) = forgeValidator.validateMint(totalSupply(), bInfo.bAsset, quantityDeposited);
require(mintValid, reason);
// Log the Vault increase - can only be done when basket is healthy
basketManager.increaseVaultBalance(bInfo.index, integrator, quantityDeposited);
// Mint the Masset
_mint(_recipient, ratioedDeposit);
emit Minted(msg.sender, _recipient, ratioedDeposit, _bAsset, quantityDeposited);
return ratioedDeposit;
}
/** @dev Mint Multi */
function _mintTo(
address[] memory _bAssets,
uint256[] memory _bAssetQuantities,
address _recipient
)
internal
returns (uint256 massetMinted)
{
require(_recipient != address(0), "Must be a valid recipient");
uint256 len = _bAssetQuantities.length;
require(len > 0 && len == _bAssets.length, "Input array mismatch");
// Load only needed bAssets in array
ForgePropsMulti memory props
= basketManager.prepareForgeBassets(_bAssets, _bAssetQuantities, true);
if(!props.isValid) return 0;
uint256 mAssetQuantity = 0;
uint256[] memory receivedQty = new uint256[](len);
// Transfer the Bassets to the integrator, update storage and calc MassetQ
for(uint256 i = 0; i < len; i++){
uint256 bAssetQuantity = _bAssetQuantities[i];
if(bAssetQuantity > 0){
// bAsset == bAssets[i] == basket.bassets[indexes[i]]
Basset memory bAsset = props.bAssets[i];
(uint256 quantityDeposited, uint256 ratioedDeposit) =
_depositTokens(bAsset.addr, bAsset.ratio, props.integrators[i], bAsset.isTransferFeeCharged, bAssetQuantity);
receivedQty[i] = quantityDeposited;
mAssetQuantity = mAssetQuantity.add(ratioedDeposit);
}
}
require(mAssetQuantity > 0, "No masset quantity to mint");
basketManager.increaseVaultBalances(props.indexes, props.integrators, receivedQty);
// Validate the proposed mint, after token transfer
(bool mintValid, string memory reason) = forgeValidator.validateMintMulti(totalSupply(), props.bAssets, receivedQty);
require(mintValid, reason);
// Mint the Masset
_mint(_recipient, mAssetQuantity);
emit MintedMulti(msg.sender, _recipient, mAssetQuantity, _bAssets, _bAssetQuantities);
return mAssetQuantity;
}
/** @dev Deposits tokens into the platform integration and returns the ratioed amount */
function _depositTokens(
address _bAsset,
uint256 _bAssetRatio,
address _integrator,
bool _erc20TransferFeeCharged,
uint256 _quantity
)
internal
returns (uint256 quantityDeposited, uint256 ratioedDeposit)
{
quantityDeposited = _depositTokens(_bAsset, _integrator, _erc20TransferFeeCharged, _quantity);
ratioedDeposit = quantityDeposited.mulRatioTruncate(_bAssetRatio);
}
/** @dev Deposits tokens into the platform integration and returns the deposited amount */
function _depositTokens(
address _bAsset,
address _integrator,
bool _erc20TransferFeeCharged,
uint256 _quantity
)
internal
returns (uint256 quantityDeposited)
{
uint256 quantityTransferred = MassetHelpers.transferTokens(msg.sender, _integrator, _bAsset, _erc20TransferFeeCharged, _quantity);
uint256 deposited = IPlatformIntegration(_integrator).deposit(_bAsset, quantityTransferred, _erc20TransferFeeCharged);
quantityDeposited = StableMath.min(deposited, _quantity);
}
/***************************************
SWAP (PUBLIC)
****************************************/
/**
* @dev Simply swaps one bAsset for another bAsset or this mAsset at a 1:1 ratio.
* bAsset <> bAsset swaps will incur a small fee (swapFee()). Swap
* is valid if it does not result in the input asset exceeding its maximum weight.
* @param _input bAsset to deposit
* @param _output Asset to receive - either a bAsset or mAsset(this)
* @param _quantity Units of input bAsset to swap
* @param _recipient Address to credit output asset
* @return output Units of output asset returned
*/
function swap(
address _input,
address _output,
uint256 _quantity,
address _recipient
)
external
nonReentrant
returns (uint256 output)
{
require(_input != address(0) && _output != address(0), "Invalid swap asset addresses");
require(_input != _output, "Cannot swap the same asset");
require(_recipient != address(0), "Missing recipient address");
require(_quantity > 0, "Invalid quantity");
// 1. If the output is this mAsset, just mint
if(_output == address(this)){
return _mintTo(_input, _quantity, _recipient);
}
// 2. Grab all relevant info from the Manager
(bool isValid, string memory reason, BassetDetails memory inputDetails, BassetDetails memory outputDetails) =
basketManager.prepareSwapBassets(_input, _output, false);
require(isValid, reason);
// 3. Deposit the input tokens
uint256 quantitySwappedIn = _depositTokens(_input, inputDetails.integrator, inputDetails.bAsset.isTransferFeeCharged, _quantity);
// 3.1. Update the input balance
basketManager.increaseVaultBalance(inputDetails.index, inputDetails.integrator, quantitySwappedIn);
// 4. Validate the swap
(bool swapValid, string memory swapValidityReason, uint256 swapOutput, bool applySwapFee) =
forgeValidator.validateSwap(totalSupply(), inputDetails.bAsset, outputDetails.bAsset, quantitySwappedIn);
require(swapValid, swapValidityReason);
// 5. Settle the swap
// 5.1. Decrease output bal
basketManager.decreaseVaultBalance(outputDetails.index, outputDetails.integrator, swapOutput);
// 5.2. Calc fee, if any
if(applySwapFee){
swapOutput = _deductSwapFee(_output, swapOutput, swapFee);
}
// 5.3. Withdraw to recipient
IPlatformIntegration(outputDetails.integrator).withdraw(_recipient, _output, swapOutput, outputDetails.bAsset.isTransferFeeCharged);
output = swapOutput;
emit Swapped(msg.sender, _input, _output, swapOutput, _recipient);
}
/**
* @dev Determines both if a trade is valid, and the expected fee or output.
* Swap is valid if it does not result in the input asset exceeding its maximum weight.
* @param _input bAsset to deposit
* @param _output Asset to receive - bAsset or mAsset(this)
* @param _quantity Units of input bAsset to swap
* @return valid Bool to signify that swap is current valid
* @return reason If swap is invalid, this is the reason
* @return output Units of _output asset the trade would return
*/
function getSwapOutput(
address _input,
address _output,
uint256 _quantity
)
external
view
returns (bool, string memory, uint256 output)
{
require(_input != address(0) && _output != address(0), "Invalid swap asset addresses");
require(_input != _output, "Cannot swap the same asset");
bool isMint = _output == address(this);
uint256 quantity = _quantity;
// 1. Get relevant asset data
(bool isValid, string memory reason, BassetDetails memory inputDetails, BassetDetails memory outputDetails) =
basketManager.prepareSwapBassets(_input, _output, isMint);
if(!isValid){
return (false, reason, 0);
}
// 2. check if trade is valid
// 2.1. If output is mAsset(this), then calculate a simple mint
if(isMint){
// Validate mint
(isValid, reason) = forgeValidator.validateMint(totalSupply(), inputDetails.bAsset, quantity);
if(!isValid) return (false, reason, 0);
// Simply cast the quantity to mAsset
output = quantity.mulRatioTruncate(inputDetails.bAsset.ratio);
return(true, "", output);
}
// 2.2. If a bAsset swap, calculate the validity, output and fee
else {
(bool swapValid, string memory swapValidityReason, uint256 swapOutput, bool applySwapFee) =
forgeValidator.validateSwap(totalSupply(), inputDetails.bAsset, outputDetails.bAsset, quantity);
if(!swapValid){
return (false, swapValidityReason, 0);
}
// 3. Return output and fee, if any
if(applySwapFee){
(, swapOutput) = _calcSwapFee(swapOutput, swapFee);
}
return (true, "", swapOutput);
}
}
/***************************************
REDEMPTION (PUBLIC)
****************************************/
/**
* @dev Credits the sender with a certain quantity of selected bAsset, in exchange for burning the
* relative mAsset quantity from the sender. Sender also incurs a small mAsset fee, if any.
* @param _bAsset Address of the bAsset to redeem
* @param _bAssetQuantity Units of the bAsset to redeem
* @return massetMinted Relative number of mAsset units burned to pay for the bAssets
*/
function redeem(
address _bAsset,
uint256 _bAssetQuantity
)
external
nonReentrant
returns (uint256 massetRedeemed)
{
return _redeemTo(_bAsset, _bAssetQuantity, msg.sender);
}
/**
* @dev Credits a recipient with a certain quantity of selected bAsset, in exchange for burning the
* relative Masset quantity from the sender. Sender also incurs a small fee, if any.
* @param _bAsset Address of the bAsset to redeem
* @param _bAssetQuantity Units of the bAsset to redeem
* @param _recipient Address to credit with withdrawn bAssets
* @return massetMinted Relative number of mAsset units burned to pay for the bAssets
*/
function redeemTo(
address _bAsset,
uint256 _bAssetQuantity,
address _recipient
)
external
nonReentrant
returns (uint256 massetRedeemed)
{
return _redeemTo(_bAsset, _bAssetQuantity, _recipient);
}
/**
* @dev Credits a recipient with a certain quantity of selected bAssets, in exchange for burning the
* relative Masset quantity from the sender. Sender also incurs a small fee, if any.
* @param _bAssets Address of the bAssets to redeem
* @param _bAssetQuantities Units of the bAssets to redeem
* @param _recipient Address to credit with withdrawn bAssets
* @return massetMinted Relative number of mAsset units burned to pay for the bAssets
*/
function redeemMulti(
address[] calldata _bAssets,
uint256[] calldata _bAssetQuantities,
address _recipient
)
external
nonReentrant
returns (uint256 massetRedeemed)
{
return _redeemTo(_bAssets, _bAssetQuantities, _recipient);
}
/**
* @dev Credits a recipient with a proportionate amount of bAssets, relative to current vault
* balance levels and desired mAsset quantity. Burns the mAsset as payment.
* @param _mAssetQuantity Quantity of mAsset to redeem
* @param _recipient Address to credit the withdrawn bAssets
*/
function redeemMasset(
uint256 _mAssetQuantity,
address _recipient
)
external
nonReentrant
{
_redeemMasset(_mAssetQuantity, _recipient);
}
/***************************************
REDEMPTION (INTERNAL)
****************************************/
/** @dev Casting to arrays for use in redeemMulti func */
function _redeemTo(
address _bAsset,
uint256 _bAssetQuantity,
address _recipient
)
internal
returns (uint256 massetRedeemed)
{
address[] memory bAssets = new address[](1);
uint256[] memory quantities = new uint256[](1);
bAssets[0] = _bAsset;
quantities[0] = _bAssetQuantity;
return _redeemTo(bAssets, quantities, _recipient);
}
/** @dev Redeem mAsset for one or more bAssets */
function _redeemTo(
address[] memory _bAssets,
uint256[] memory _bAssetQuantities,
address _recipient
)
internal
returns (uint256 massetRedeemed)
{
require(_recipient != address(0), "Must be a valid recipient");
uint256 bAssetCount = _bAssetQuantities.length;
require(bAssetCount > 0 && bAssetCount == _bAssets.length, "Input array mismatch");
// Get high level basket info
Basket memory basket = basketManager.getBasket();
// Prepare relevant data
ForgePropsMulti memory props = basketManager.prepareForgeBassets(_bAssets, _bAssetQuantities, false);
if(!props.isValid) return 0;
// Validate redemption
(bool redemptionValid, string memory reason, bool applyFee) =
forgeValidator.validateRedemption(basket.failed, totalSupply(), basket.bassets, props.indexes, _bAssetQuantities);
require(redemptionValid, reason);
uint256 mAssetQuantity = 0;
// Calc total redeemed mAsset quantity
for(uint256 i = 0; i < bAssetCount; i++){
uint256 bAssetQuantity = _bAssetQuantities[i];
if(bAssetQuantity > 0){
// Calc equivalent mAsset amount
uint256 ratioedBasset = bAssetQuantity.mulRatioTruncateCeil(props.bAssets[i].ratio);
mAssetQuantity = mAssetQuantity.add(ratioedBasset);
}
}
require(mAssetQuantity > 0, "Must redeem some bAssets");
// Redemption has fee? Fetch the rate
uint256 fee = applyFee ? swapFee : 0;
// Apply fees, burn mAsset and return bAsset to recipient
_settleRedemption(_recipient, mAssetQuantity, props.bAssets, _bAssetQuantities, props.indexes, props.integrators, fee);
emit Redeemed(msg.sender, _recipient, mAssetQuantity, _bAssets, _bAssetQuantities);
return mAssetQuantity;
}
/** @dev Redeem mAsset for a multiple bAssets */
function _redeemMasset(
uint256 _mAssetQuantity,
address _recipient
)
internal
{
require(_recipient != address(0), "Must be a valid recipient");
require(_mAssetQuantity > 0, "Invalid redemption quantity");
// Fetch high level details
RedeemPropsMulti memory props = basketManager.prepareRedeemMulti();
uint256 colRatio = StableMath.min(props.colRatio, StableMath.getFullScale());
// Ensure payout is related to the collateralised mAsset quantity
uint256 collateralisedMassetQuantity = _mAssetQuantity.mulTruncate(colRatio);
// Calculate redemption quantities
(bool redemptionValid, string memory reason, uint256[] memory bAssetQuantities) =
forgeValidator.calculateRedemptionMulti(collateralisedMassetQuantity, props.bAssets);
require(redemptionValid, reason);
// Apply fees, burn mAsset and return bAsset to recipient
_settleRedemption(_recipient, _mAssetQuantity, props.bAssets, bAssetQuantities, props.indexes, props.integrators, redemptionFee);
emit RedeemedMasset(msg.sender, _recipient, _mAssetQuantity);
}
/**
* @dev Internal func to update contract state post-redemption
* @param _recipient Recipient of the bAssets
* @param _mAssetQuantity Total amount of mAsset to burn from sender
* @param _bAssets Array of bAssets to redeem
* @param _bAssetQuantities Array of bAsset quantities
* @param _indices Matching indices for the bAsset array
* @param _integrators Matching integrators for the bAsset array
* @param _feeRate Apply a fee to this redemption?
*/
function _settleRedemption(
address _recipient,
uint256 _mAssetQuantity,
Basset[] memory _bAssets,
uint256[] memory _bAssetQuantities,
uint8[] memory _indices,
address[] memory _integrators,
uint256 _feeRate
) internal {
// Burn the full amount of Masset
_burn(msg.sender, _mAssetQuantity);
// Reduce the amount of bAssets marked in the vault
basketManager.decreaseVaultBalances(_indices, _integrators, _bAssetQuantities);
// Transfer the Bassets to the recipient
uint256 bAssetCount = _bAssets.length;
for(uint256 i = 0; i < bAssetCount; i++){
address bAsset = _bAssets[i].addr;
uint256 q = _bAssetQuantities[i];
if(q > 0){
// Deduct the redemption fee, if any
q = _deductSwapFee(bAsset, q, _feeRate);
// Transfer the Bassets to the user
IPlatformIntegration(_integrators[i]).withdraw(_recipient, bAsset, q, _bAssets[i].isTransferFeeCharged);
}
}
}
/***************************************
INTERNAL
****************************************/
/**
* @dev Pay the forging fee by burning relative amount of mAsset
* @param _bAssetQuantity Exact amount of bAsset being swapped out
*/
function _deductSwapFee(address _asset, uint256 _bAssetQuantity, uint256 _feeRate)
private
returns (uint256 outputMinusFee)
{
outputMinusFee = _bAssetQuantity;
if(_feeRate > 0){
(uint256 fee, uint256 output) = _calcSwapFee(_bAssetQuantity, _feeRate);
outputMinusFee = output;
emit PaidFee(msg.sender, _asset, fee);
}
}
/**
* @dev Pay the forging fee by burning relative amount of mAsset
* @param _bAssetQuantity Exact amount of bAsset being swapped out
*/
function _calcSwapFee(uint256 _bAssetQuantity, uint256 _feeRate)
private
pure
returns (uint256 feeAmount, uint256 outputMinusFee)
{
// e.g. for 500 massets.
// feeRate == 1% == 1e16. _quantity == 5e20.
// (5e20 * 1e16) / 1e18 = 5e18
feeAmount = _bAssetQuantity.mulTruncate(_feeRate);
outputMinusFee = _bAssetQuantity.sub(feeAmount);
}
/***************************************
STATE
****************************************/
/**
* @dev Upgrades the version of ForgeValidator protocol. Governor can do this
* only while ForgeValidator is unlocked.
* @param _newForgeValidator Address of the new ForgeValidator
*/
function upgradeForgeValidator(address _newForgeValidator)
external
onlyGovernor
{
require(!forgeValidatorLocked, "Must be allowed to upgrade");
require(_newForgeValidator != address(0), "Must be non null address");
forgeValidator = IForgeValidator(_newForgeValidator);
emit ForgeValidatorChanged(_newForgeValidator);
}
/**
* @dev Locks the ForgeValidator into it's final form. Called by Governor
*/
function lockForgeValidator()
external
onlyGovernor
{
forgeValidatorLocked = true;
}
/**
* @dev Set the ecosystem fee for redeeming a mAsset
* @param _swapFee Fee calculated in (%/100 * 1e18)
*/
function setSwapFee(uint256 _swapFee)
external
onlyGovernor
{
require(_swapFee <= MAX_FEE, "Rate must be within bounds");
swapFee = _swapFee;
emit SwapFeeChanged(_swapFee);
}
/**
* @dev Set the ecosystem fee for redeeming a mAsset
* @param _redemptionFee Fee calculated in (%/100 * 1e18)
*/
function setRedemptionFee(uint256 _redemptionFee)
external
onlyGovernor
{
require(_redemptionFee <= MAX_FEE, "Rate must be within bounds");
redemptionFee = _redemptionFee;
emit RedemptionFeeChanged(_redemptionFee);
}
/**
* @dev Gets the address of the BasketManager for this mAsset
* @return basketManager Address
*/
function getBasketManager()
external
view
returns (address)
{
return address(basketManager);
}
/***************************************
INFLATION
****************************************/
/**
* @dev Collects the interest generated from the Basket, minting a relative
* amount of mAsset and sending it over to the SavingsManager.
* @return totalInterestGained Equivalent amount of mAsset units that have been generated
* @return newSupply New total mAsset supply
*/
function collectInterest()
external
onlySavingsManager
nonReentrant
returns (uint256 totalInterestGained, uint256 newSupply)
{
(uint256 interestCollected, uint256[] memory gains) = basketManager.collectInterest();
// mint new mAsset to sender
_mint(msg.sender, interestCollected);
emit MintedMulti(address(this), address(this), interestCollected, new address[](0), gains);
return (interestCollected, totalSupply());
}
// RELEASE 1.1 VARS
uint256 public redemptionFee;
}File 4 of 7: BasketManagerProxy
pragma solidity 0.5.16;
/**
* @title Proxy
* @dev Implements delegation of calls to other contracts, with proper
* forwarding of return values and bubbling of failures.
* It defines a fallback function that delegates all calls to the address
* returned by the abstract _implementation() internal function.
*/
contract Proxy {
/**
* @dev Fallback function.
* Implemented entirely in `_fallback`.
*/
function () payable external {
_fallback();
}
/**
* @return The Address of the implementation.
*/
function _implementation() internal view returns (address);
/**
* @dev Delegates execution to an implementation contract.
* This is a low level function that doesn't return to its internal call site.
* It will return to the external caller whatever the implementation returns.
* @param implementation Address to delegate.
*/
function _delegate(address implementation) internal {
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize)
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas, implementation, 0, calldatasize, 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize)
switch result
// delegatecall returns 0 on error.
case 0 { revert(0, returndatasize) }
default { return(0, returndatasize) }
}
}
/**
* @dev Function that is run as the first thing in the fallback function.
* Can be redefined in derived contracts to add functionality.
* Redefinitions must call super._willFallback().
*/
function _willFallback() internal {
}
/**
* @dev fallback implementation.
* Extracted to enable manual triggering.
*/
function _fallback() internal {
_willFallback();
_delegate(_implementation());
}
}
/**
* Utility library of inline functions on addresses
*
* Source https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-solidity/v2.1.3/contracts/utils/Address.sol
* This contract is copied here and renamed from the original to avoid clashes in the compiled artifacts
* when the user imports a zos-lib contract (that transitively causes this contract to be compiled and added to the
* build/artifacts folder) as well as the vanilla Address implementation from an openzeppelin version.
*/
library OpenZeppelinUpgradesAddress {
/**
* Returns whether the target address is a contract
* @dev This function will return false if invoked during the constructor of a contract,
* as the code is not actually created until after the constructor finishes.
* @param account address of the account to check
* @return whether the target address is a contract
*/
function isContract(address account) internal view returns (bool) {
uint256 size;
// XXX Currently there is no better way to check if there is a contract in an address
// than to check the size of the code at that address.
// See https://ethereum.stackexchange.com/a/14016/36603
// for more details about how this works.
// TODO Check this again before the Serenity release, because all addresses will be
// contracts then.
// solhint-disable-next-line no-inline-assembly
assembly { size := extcodesize(account) }
return size > 0;
}
}
/**
* @title BaseUpgradeabilityProxy
* @dev This contract implements a proxy that allows to change the
* implementation address to which it will delegate.
* Such a change is called an implementation upgrade.
*/
contract BaseUpgradeabilityProxy is Proxy {
/**
* @dev Emitted when the implementation is upgraded.
* @param implementation Address of the new implementation.
*/
event Upgraded(address indexed implementation);
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @dev Returns the current implementation.
* @return Address of the current implementation
*/
function _implementation() internal view returns (address impl) {
bytes32 slot = IMPLEMENTATION_SLOT;
assembly {
impl := sload(slot)
}
}
/**
* @dev Upgrades the proxy to a new implementation.
* @param newImplementation Address of the new implementation.
*/
function _upgradeTo(address newImplementation) internal {
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
}
/**
* @dev Sets the implementation address of the proxy.
* @param newImplementation Address of the new implementation.
*/
function _setImplementation(address newImplementation) internal {
require(OpenZeppelinUpgradesAddress.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address");
bytes32 slot = IMPLEMENTATION_SLOT;
assembly {
sstore(slot, newImplementation)
}
}
}
/**
* @title UpgradeabilityProxy
* @dev Extends BaseUpgradeabilityProxy with a constructor for initializing
* implementation and init data.
*/
contract UpgradeabilityProxy is BaseUpgradeabilityProxy {
/**
* @dev Contract constructor.
* @param _logic Address of the initial implementation.
* @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
* It should include the signature and the parameters of the function to be called, as described in
* https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
* This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
*/
constructor(address _logic, bytes memory _data) public payable {
assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1));
_setImplementation(_logic);
if(_data.length > 0) {
(bool success,) = _logic.delegatecall(_data);
require(success);
}
}
}
/**
* @title BaseAdminUpgradeabilityProxy
* @dev This contract combines an upgradeability proxy with an authorization
* mechanism for administrative tasks.
* All external functions in this contract must be guarded by the
* `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity
* feature proposal that would enable this to be done automatically.
*/
contract BaseAdminUpgradeabilityProxy is BaseUpgradeabilityProxy {
/**
* @dev Emitted when the administration has been transferred.
* @param previousAdmin Address of the previous admin.
* @param newAdmin Address of the new admin.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Modifier to check whether the `msg.sender` is the admin.
* If it is, it will run the function. Otherwise, it will delegate the call
* to the implementation.
*/
modifier ifAdmin() {
if (msg.sender == _admin()) {
_;
} else {
_fallback();
}
}
/**
* @return The address of the proxy admin.
*/
function admin() external ifAdmin returns (address) {
return _admin();
}
/**
* @return The address of the implementation.
*/
function implementation() external ifAdmin returns (address) {
return _implementation();
}
/**
* @dev Changes the admin of the proxy.
* Only the current admin can call this function.
* @param newAdmin Address to transfer proxy administration to.
*/
function changeAdmin(address newAdmin) external ifAdmin {
require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address");
emit AdminChanged(_admin(), newAdmin);
_setAdmin(newAdmin);
}
/**
* @dev Upgrade the backing implementation of the proxy.
* Only the admin can call this function.
* @param newImplementation Address of the new implementation.
*/
function upgradeTo(address newImplementation) external ifAdmin {
_upgradeTo(newImplementation);
}
/**
* @dev Upgrade the backing implementation of the proxy and call a function
* on the new implementation.
* This is useful to initialize the proxied contract.
* @param newImplementation Address of the new implementation.
* @param data Data to send as msg.data in the low level call.
* It should include the signature and the parameters of the function to be called, as described in
* https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
*/
function upgradeToAndCall(address newImplementation, bytes calldata data) payable external ifAdmin {
_upgradeTo(newImplementation);
(bool success,) = newImplementation.delegatecall(data);
require(success);
}
/**
* @return The admin slot.
*/
function _admin() internal view returns (address adm) {
bytes32 slot = ADMIN_SLOT;
assembly {
adm := sload(slot)
}
}
/**
* @dev Sets the address of the proxy admin.
* @param newAdmin Address of the new proxy admin.
*/
function _setAdmin(address newAdmin) internal {
bytes32 slot = ADMIN_SLOT;
assembly {
sstore(slot, newAdmin)
}
}
/**
* @dev Only fall back when the sender is not the admin.
*/
function _willFallback() internal {
require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin");
super._willFallback();
}
}
/**
* @title InitializableUpgradeabilityProxy
* @dev Extends BaseUpgradeabilityProxy with an initializer for initializing
* implementation and init data.
*/
contract InitializableUpgradeabilityProxy is BaseUpgradeabilityProxy {
/**
* @dev Contract initializer.
* @param _logic Address of the initial implementation.
* @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
* It should include the signature and the parameters of the function to be called, as described in
* https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
* This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
*/
function initialize(address _logic, bytes memory _data) public payable {
require(_implementation() == address(0));
assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1));
_setImplementation(_logic);
if(_data.length > 0) {
(bool success,) = _logic.delegatecall(_data);
require(success);
}
}
}
/**
* @title InitializableAdminUpgradeabilityProxy
* @dev Extends from BaseAdminUpgradeabilityProxy with an initializer for
* initializing the implementation, admin, and init data.
*/
contract InitializableAdminUpgradeabilityProxy is BaseAdminUpgradeabilityProxy, InitializableUpgradeabilityProxy {
/**
* Contract initializer.
* @param _logic address of the initial implementation.
* @param _admin Address of the proxy administrator.
* @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
* It should include the signature and the parameters of the function to be called, as described in
* https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
* This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
*/
function initialize(address _logic, address _admin, bytes memory _data) public payable {
require(_implementation() == address(0));
InitializableUpgradeabilityProxy.initialize(_logic, _data);
assert(ADMIN_SLOT == bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1));
_setAdmin(_admin);
}
}
/**
* @notice MassetProxy delegates calls to a Masset implementation
* @dev Extending on OpenZeppelin's InitializableAdminUpgradabilityProxy
* means that the proxy is upgradable through a ProxyAdmin. MassetProxy upgrades
* are implemented by a DelayedProxyAdmin, which enforces a 1 week opt-out period.
* All upgrades are governed through the current mStable governance.
*/
contract MassetProxy is InitializableAdminUpgradeabilityProxy {
}
/**
* @notice BasketManagerProxy delegates calls to a BasketManager implementation
* @dev Extending on OpenZeppelin's InitializableAdminUpgradabilityProxy
* means that the proxy is upgradable through a ProxyAdmin. BasketManagerProxy upgrades
* are implemented by a DelayedProxyAdmin, which enforces a 1 week opt-out period.
* All upgrades are governed through the current mStable governance.
*/
contract BasketManagerProxy is InitializableAdminUpgradeabilityProxy {
}File 5 of 7: BasketManager
pragma solidity 0.5.16;
pragma experimental ABIEncoderV2;
interface IPlatformIntegration {
/**
* @dev Deposit the given bAsset to Lending platform
* @param _bAsset bAsset address
* @param _amount Amount to deposit
*/
function deposit(address _bAsset, uint256 _amount, bool isTokenFeeCharged)
external returns (uint256 quantityDeposited);
/**
* @dev Withdraw given bAsset from Lending platform
*/
function withdraw(address _receiver, address _bAsset, uint256 _amount, bool _isTokenFeeCharged) external;
/**
* @dev Returns the current balance of the given bAsset
*/
function checkBalance(address _bAsset) external returns (uint256 balance);
}
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
contract InitializableModuleKeys {
// Governance
bytes32 internal KEY_GOVERNANCE;
bytes32 internal KEY_STAKING;
bytes32 internal KEY_PROXY_ADMIN;
// mStable
bytes32 internal KEY_ORACLE_HUB;
bytes32 internal KEY_MANAGER;
bytes32 internal KEY_RECOLLATERALISER;
bytes32 internal KEY_META_TOKEN;
bytes32 internal KEY_SAVINGS_MANAGER;
/**
* @dev Initialize function for upgradable proxy contracts. This function should be called
* via Proxy to initialize constants in the Proxy contract.
*/
function _initialize() internal {
// keccak256() values are evaluated only once at the time of this function call.
// Hence, no need to assign hard-coded values to these variables.
KEY_GOVERNANCE = keccak256("Governance");
KEY_STAKING = keccak256("Staking");
KEY_PROXY_ADMIN = keccak256("ProxyAdmin");
KEY_ORACLE_HUB = keccak256("OracleHub");
KEY_MANAGER = keccak256("Manager");
KEY_RECOLLATERALISER = keccak256("Recollateraliser");
KEY_META_TOKEN = keccak256("MetaToken");
KEY_SAVINGS_MANAGER = keccak256("SavingsManager");
}
}
interface INexus {
function governor() external view returns (address);
function getModule(bytes32 key) external view returns (address);
function proposeModule(bytes32 _key, address _addr) external;
function cancelProposedModule(bytes32 _key) external;
function acceptProposedModule(bytes32 _key) external;
function acceptProposedModules(bytes32[] calldata _keys) external;
function requestLockModule(bytes32 _key) external;
function cancelLockModule(bytes32 _key) external;
function lockModule(bytes32 _key) external;
}
contract InitializableModule is InitializableModuleKeys {
INexus public nexus;
/**
* @dev Modifier to allow function calls only from the Governor.
*/
modifier onlyGovernor() {
require(msg.sender == _governor(), "Only governor can execute");
_;
}
/**
* @dev Modifier to allow function calls only from the Governance.
* Governance is either Governor address or Governance address.
*/
modifier onlyGovernance() {
require(
msg.sender == _governor() || msg.sender == _governance(),
"Only governance can execute"
);
_;
}
/**
* @dev Modifier to allow function calls only from the ProxyAdmin.
*/
modifier onlyProxyAdmin() {
require(
msg.sender == _proxyAdmin(), "Only ProxyAdmin can execute"
);
_;
}
/**
* @dev Modifier to allow function calls only from the Manager.
*/
modifier onlyManager() {
require(msg.sender == _manager(), "Only manager can execute");
_;
}
/**
* @dev Initialization function for upgradable proxy contracts
* @param _nexus Nexus contract address
*/
function _initialize(address _nexus) internal {
require(_nexus != address(0), "Nexus address is zero");
nexus = INexus(_nexus);
InitializableModuleKeys._initialize();
}
/**
* @dev Returns Governor address from the Nexus
* @return Address of Governor Contract
*/
function _governor() internal view returns (address) {
return nexus.governor();
}
/**
* @dev Returns Governance Module address from the Nexus
* @return Address of the Governance (Phase 2)
*/
function _governance() internal view returns (address) {
return nexus.getModule(KEY_GOVERNANCE);
}
/**
* @dev Return Staking Module address from the Nexus
* @return Address of the Staking Module contract
*/
function _staking() internal view returns (address) {
return nexus.getModule(KEY_STAKING);
}
/**
* @dev Return ProxyAdmin Module address from the Nexus
* @return Address of the ProxyAdmin Module contract
*/
function _proxyAdmin() internal view returns (address) {
return nexus.getModule(KEY_PROXY_ADMIN);
}
/**
* @dev Return MetaToken Module address from the Nexus
* @return Address of the MetaToken Module contract
*/
function _metaToken() internal view returns (address) {
return nexus.getModule(KEY_META_TOKEN);
}
/**
* @dev Return OracleHub Module address from the Nexus
* @return Address of the OracleHub Module contract
*/
function _oracleHub() internal view returns (address) {
return nexus.getModule(KEY_ORACLE_HUB);
}
/**
* @dev Return Manager Module address from the Nexus
* @return Address of the Manager Module contract
*/
function _manager() internal view returns (address) {
return nexus.getModule(KEY_MANAGER);
}
/**
* @dev Return SavingsManager Module address from the Nexus
* @return Address of the SavingsManager Module contract
*/
function _savingsManager() internal view returns (address) {
return nexus.getModule(KEY_SAVINGS_MANAGER);
}
/**
* @dev Return Recollateraliser Module address from the Nexus
* @return Address of the Recollateraliser Module contract (Phase 2)
*/
function _recollateraliser() internal view returns (address) {
return nexus.getModule(KEY_RECOLLATERALISER);
}
}
contract InitializablePausableModule is InitializableModule {
/**
* @dev Emitted when the pause is triggered by Governor
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by Governor
*/
event Unpaused(address account);
bool private _paused = false;
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*/
modifier whenNotPaused() {
require(!_paused, "Pausable: paused");
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*/
modifier whenPaused() {
require(_paused, "Pausable: not paused");
_;
}
/**
* @dev Initializes the contract in unpaused state.
* Hooks into the Module to give the Governor ability to pause
* @param _nexus Nexus contract address
*/
function _initialize(address _nexus) internal {
InitializableModule._initialize(_nexus);
_paused = false;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
* @return Returns `true` when paused, otherwise `false`
*/
function paused() external view returns (bool) {
return _paused;
}
/**
* @dev Called by the Governor to pause, triggers stopped state.
*/
function pause() external onlyGovernor whenNotPaused {
_paused = true;
emit Paused(msg.sender);
}
/**
* @dev Called by Governor to unpause, returns to normal state.
*/
function unpause() external onlyGovernor whenPaused {
_paused = false;
emit Unpaused(msg.sender);
}
}
interface MassetStructs {
/** @dev Stores high level basket info */
struct Basket {
/** @dev Array of Bassets currently active */
Basset[] bassets;
/** @dev Max number of bAssets that can be present in any Basket */
uint8 maxBassets;
/** @dev Some bAsset is undergoing re-collateralisation */
bool undergoingRecol;
/**
* @dev In the event that we do not raise enough funds from the auctioning of a failed Basset,
* The Basket is deemed as failed, and is undercollateralised to a certain degree.
* The collateralisation ratio is used to calc Masset burn rate.
*/
bool failed;
uint256 collateralisationRatio;
}
/** @dev Stores bAsset info. The struct takes 5 storage slots per Basset */
struct Basset {
/** @dev Address of the bAsset */
address addr;
/** @dev Status of the basset, */
BassetStatus status; // takes uint8 datatype (1 byte) in storage
/** @dev An ERC20 can charge transfer fee, for example USDT, DGX tokens. */
bool isTransferFeeCharged; // takes a byte in storage
/**
* @dev 1 Basset * ratio / ratioScale == x Masset (relative value)
* If ratio == 10e8 then 1 bAsset = 10 mAssets
* A ratio is divised as 10^(18-tokenDecimals) * measurementMultiple(relative value of 1 base unit)
*/
uint256 ratio;
/** @dev Target weights of the Basset (100% == 1e18) */
uint256 maxWeight;
/** @dev Amount of the Basset that is held in Collateral */
uint256 vaultBalance;
}
/** @dev Status of the Basset - has it broken its peg? */
enum BassetStatus {
Default,
Normal,
BrokenBelowPeg,
BrokenAbovePeg,
Blacklisted,
Liquidating,
Liquidated,
Failed
}
/** @dev Internal details on Basset */
struct BassetDetails {
Basset bAsset;
address integrator;
uint8 index;
}
/** @dev All details needed to Forge with multiple bAssets */
struct ForgePropsMulti {
bool isValid; // Flag to signify that forge bAssets have passed validity check
Basset[] bAssets;
address[] integrators;
uint8[] indexes;
}
/** @dev All details needed for proportionate Redemption */
struct RedeemPropsMulti {
uint256 colRatio;
Basset[] bAssets;
address[] integrators;
uint8[] indexes;
}
}
contract IBasketManager is MassetStructs {
/** @dev Setters for mAsset to update balances */
function increaseVaultBalance(
uint8 _bAsset,
address _integrator,
uint256 _increaseAmount) external;
function increaseVaultBalances(
uint8[] calldata _bAsset,
address[] calldata _integrator,
uint256[] calldata _increaseAmount) external;
function decreaseVaultBalance(
uint8 _bAsset,
address _integrator,
uint256 _decreaseAmount) external;
function decreaseVaultBalances(
uint8[] calldata _bAsset,
address[] calldata _integrator,
uint256[] calldata _decreaseAmount) external;
function collectInterest() external
returns (uint256 interestCollected, uint256[] memory gains);
/** @dev Setters for Gov to update Basket composition */
function addBasset(
address _basset,
address _integration,
bool _isTransferFeeCharged) external returns (uint8 index);
function setBasketWeights(address[] calldata _bassets, uint256[] calldata _weights) external;
function setTransferFeesFlag(address _bAsset, bool _flag) external;
/** @dev Getters to retrieve Basket information */
function getBasket() external view returns (Basket memory b);
function prepareForgeBasset(address _token, uint256 _amt, bool _mint) external
returns (bool isValid, BassetDetails memory bInfo);
function prepareSwapBassets(address _input, address _output, bool _isMint) external view
returns (bool, string memory, BassetDetails memory, BassetDetails memory);
function prepareForgeBassets(address[] calldata _bAssets, uint256[] calldata _amts, bool _mint) external
returns (ForgePropsMulti memory props);
function prepareRedeemMulti() external view
returns (RedeemPropsMulti memory props);
function getBasset(address _token) external view
returns (Basset memory bAsset);
function getBassets() external view
returns (Basset[] memory bAssets, uint256 len);
/** @dev Recollateralisation */
function handlePegLoss(address _basset, bool _belowPeg) external returns (bool actioned);
function negateIsolation(address _basset) external;
}
contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
*/
bool private initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private initializing;
/**
* @dev Modifier to use in the initializer function of a contract.
*/
modifier initializer() {
require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized");
bool isTopLevelCall = !initializing;
if (isTopLevelCall) {
initializing = true;
initialized = true;
}
_;
if (isTopLevelCall) {
initializing = false;
}
}
/// @dev Returns true if and only if the function is running in the constructor
function isConstructor() private view returns (bool) {
// extcodesize checks the size of the code stored in an address, and
// address returns the current address. Since the code is still not
// deployed when running a constructor, any checks on its code size will
// yield zero, making it an effective way to detect if a contract is
// under construction or not.
address self = address(this);
uint256 cs;
assembly { cs := extcodesize(self) }
return cs == 0;
}
// Reserved storage space to allow for layout changes in the future.
uint256[50] private ______gap;
}
contract ModuleKeys {
// Governance
// ===========
// Phases
// keccak256("Governance"); // 2.x
bytes32 internal constant KEY_GOVERNANCE = 0x9409903de1e6fd852dfc61c9dacb48196c48535b60e25abf92acc92dd689078d;
//keccak256("Staking"); // 1.2
bytes32 internal constant KEY_STAKING = 0x1df41cd916959d1163dc8f0671a666ea8a3e434c13e40faef527133b5d167034;
//keccak256("ProxyAdmin"); // 1.0
bytes32 internal constant KEY_PROXY_ADMIN = 0x96ed0203eb7e975a4cbcaa23951943fa35c5d8288117d50c12b3d48b0fab48d1;
// mStable
// =======
// keccak256("OracleHub"); // 1.2
bytes32 internal constant KEY_ORACLE_HUB = 0x8ae3a082c61a7379e2280f3356a5131507d9829d222d853bfa7c9fe1200dd040;
// keccak256("Manager"); // 1.2
bytes32 internal constant KEY_MANAGER = 0x6d439300980e333f0256d64be2c9f67e86f4493ce25f82498d6db7f4be3d9e6f;
//keccak256("Recollateraliser"); // 2.x
bytes32 internal constant KEY_RECOLLATERALISER = 0x39e3ed1fc335ce346a8cbe3e64dd525cf22b37f1e2104a755e761c3c1eb4734f;
//keccak256("MetaToken"); // 1.1
bytes32 internal constant KEY_META_TOKEN = 0xea7469b14936af748ee93c53b2fe510b9928edbdccac3963321efca7eb1a57a2;
// keccak256("SavingsManager"); // 1.0
bytes32 internal constant KEY_SAVINGS_MANAGER = 0x12fe936c77a1e196473c4314f3bed8eeac1d757b319abb85bdda70df35511bf1;
}
interface IBasicToken {
function decimals() external view returns (uint8);
}
library CommonHelpers {
/**
* @notice Fetch the `decimals()` from an ERC20 token
* @dev Grabs the `decimals()` from a contract and fails if
* the decimal value does not live within a certain range
* @param _token Address of the ERC20 token
* @return uint256 Decimals of the ERC20 token
*/
function getDecimals(address _token)
internal
view
returns (uint256) {
uint256 decimals = IBasicToken(_token).decimals();
require(decimals >= 4 && decimals <= 18, "Token must have sufficient decimal places");
return decimals;
}
}
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*
* _Available since v2.4.0._
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*
* _Available since v2.4.0._
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*
* _Available since v2.4.0._
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
// for accounts without code, i.e. `keccak256('')`
bytes32 codehash;
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
// solhint-disable-next-line no-inline-assembly
assembly { codehash := extcodehash(account) }
return (codehash != accountHash && codehash != 0x0);
}
/**
* @dev Converts an `address` into `address payable`. Note that this is
* simply a type cast: the actual underlying value is not changed.
*
* _Available since v2.4.0._
*/
function toPayable(address account) internal pure returns (address payable) {
return address(uint160(account));
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*
* _Available since v2.4.0._
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
// solhint-disable-next-line avoid-call-value
(bool success, ) = recipient.call.value(amount)("");
require(success, "Address: unable to send value, recipient may have reverted");
}
}
library SafeERC20 {
using SafeMath for uint256;
using Address for address;
function safeTransfer(IERC20 token, address to, uint256 value) internal {
callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require((value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).add(value);
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves.
// A Solidity high level call has three parts:
// 1. The target address is checked to verify it contains contract code
// 2. The call itself is made, and success asserted
// 3. The return value is decoded, which in turn checks the size of the returned data.
// solhint-disable-next-line max-line-length
require(address(token).isContract(), "SafeERC20: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = address(token).call(data);
require(success, "SafeERC20: low-level call failed");
if (returndata.length > 0) { // Return data is optional
// solhint-disable-next-line max-line-length
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
library StableMath {
using SafeMath for uint256;
/**
* @dev Scaling unit for use in specific calculations,
* where 1 * 10**18, or 1e18 represents a unit '1'
*/
uint256 private constant FULL_SCALE = 1e18;
/**
* @notice Token Ratios are used when converting between units of bAsset, mAsset and MTA
* Reasoning: Takes into account token decimals, and difference in base unit (i.e. grams to Troy oz for gold)
* @dev bAsset ratio unit for use in exact calculations,
* where (1 bAsset unit * bAsset.ratio) / ratioScale == x mAsset unit
*/
uint256 private constant RATIO_SCALE = 1e8;
/**
* @dev Provides an interface to the scaling unit
* @return Scaling unit (1e18 or 1 * 10**18)
*/
function getFullScale() internal pure returns (uint256) {
return FULL_SCALE;
}
/**
* @dev Provides an interface to the ratio unit
* @return Ratio scale unit (1e8 or 1 * 10**8)
*/
function getRatioScale() internal pure returns (uint256) {
return RATIO_SCALE;
}
/**
* @dev Scales a given integer to the power of the full scale.
* @param x Simple uint256 to scale
* @return Scaled value a to an exact number
*/
function scaleInteger(uint256 x)
internal
pure
returns (uint256)
{
return x.mul(FULL_SCALE);
}
/***************************************
PRECISE ARITHMETIC
****************************************/
/**
* @dev Multiplies two precise units, and then truncates by the full scale
* @param x Left hand input to multiplication
* @param y Right hand input to multiplication
* @return Result after multiplying the two inputs and then dividing by the shared
* scale unit
*/
function mulTruncate(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
return mulTruncateScale(x, y, FULL_SCALE);
}
/**
* @dev Multiplies two precise units, and then truncates by the given scale. For example,
* when calculating 90% of 10e18, (10e18 * 9e17) / 1e18 = (9e36) / 1e18 = 9e18
* @param x Left hand input to multiplication
* @param y Right hand input to multiplication
* @param scale Scale unit
* @return Result after multiplying the two inputs and then dividing by the shared
* scale unit
*/
function mulTruncateScale(uint256 x, uint256 y, uint256 scale)
internal
pure
returns (uint256)
{
uint256 z = x.mul(y);
return z.div(scale);
}
/**
* @dev Multiplies two precise units, and then truncates by the full scale, rounding up the result
* @param x Left hand input to multiplication
* @param y Right hand input to multiplication
* @return Result after multiplying the two inputs and then dividing by the shared
* scale unit, rounded up to the closest base unit.
*/
function mulTruncateCeil(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
uint256 scaled = x.mul(y);
uint256 ceil = scaled.add(FULL_SCALE.sub(1));
return ceil.div(FULL_SCALE);
}
/**
* @dev Precisely divides two units, by first scaling the left hand operand. Useful
* for finding percentage weightings, i.e. 8e18/10e18 = 80% (or 8e17)
* @param x Left hand input to division
* @param y Right hand input to division
* @return Result after multiplying the left operand by the scale, and
* executing the division on the right hand input.
*/
function divPrecisely(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
uint256 z = x.mul(FULL_SCALE);
return z.div(y);
}
/***************************************
RATIO FUNCS
****************************************/
/**
* @dev Multiplies and truncates a token ratio, essentially flooring the result
* i.e. How much mAsset is this bAsset worth?
* @param x Left hand operand to multiplication (i.e Exact quantity)
* @param ratio bAsset ratio
* @return Result after multiplying the two inputs and then dividing by the ratio scale
*/
function mulRatioTruncate(uint256 x, uint256 ratio)
internal
pure
returns (uint256 c)
{
return mulTruncateScale(x, ratio, RATIO_SCALE);
}
/**
* @dev Multiplies and truncates a token ratio, rounding up the result
* i.e. How much mAsset is this bAsset worth?
* @param x Left hand input to multiplication (i.e Exact quantity)
* @param ratio bAsset ratio
* @return Result after multiplying the two inputs and then dividing by the shared
* ratio scale, rounded up to the closest base unit.
*/
function mulRatioTruncateCeil(uint256 x, uint256 ratio)
internal
pure
returns (uint256)
{
// e.g. How much mAsset should I burn for this bAsset (x)?
// 1e18 * 1e8 = 1e26
uint256 scaled = x.mul(ratio);
// 1e26 + 9.99e7 = 100..00.999e8
uint256 ceil = scaled.add(RATIO_SCALE.sub(1));
// return 100..00.999e8 / 1e8 = 1e18
return ceil.div(RATIO_SCALE);
}
/**
* @dev Precisely divides two ratioed units, by first scaling the left hand operand
* i.e. How much bAsset is this mAsset worth?
* @param x Left hand operand in division
* @param ratio bAsset ratio
* @return Result after multiplying the left operand by the scale, and
* executing the division on the right hand input.
*/
function divRatioPrecisely(uint256 x, uint256 ratio)
internal
pure
returns (uint256 c)
{
// e.g. 1e14 * 1e8 = 1e22
uint256 y = x.mul(RATIO_SCALE);
// return 1e22 / 1e12 = 1e10
return y.div(ratio);
}
/***************************************
HELPERS
****************************************/
/**
* @dev Calculates minimum of two numbers
* @param x Left hand input
* @param y Right hand input
* @return Minimum of the two inputs
*/
function min(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
return x > y ? y : x;
}
/**
* @dev Calculated maximum of two numbers
* @param x Left hand input
* @param y Right hand input
* @return Maximum of the two inputs
*/
function max(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
return x > y ? x : y;
}
/**
* @dev Clamps a value to an upper bound
* @param x Left hand input
* @param upperBound Maximum possible value to return
* @return Input x clamped to a maximum value, upperBound
*/
function clamp(uint256 x, uint256 upperBound)
internal
pure
returns (uint256)
{
return x > upperBound ? upperBound : x;
}
}
contract InitializableReentrancyGuard {
bool private _notEntered;
function _initialize() internal {
// Storing an initial 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 percetange 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.
_notEntered = true;
}
/**
* @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(_notEntered, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_notEntered = false;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_notEntered = true;
}
}
/**
* @title BasketManager
* @notice Manages the Basket composition for a particular mAsset. Feeds all required
* basket data to the mAsset and is responsible for keeping accurate records.
* BasketManager can also optimise lending pool integrations and perform
* re-collateralisation on failed bAssets.
* @dev VERSION: 1.0
* DATE: 2020-05-05
*/
contract BasketManager is
Initializable,
IBasketManager,
InitializablePausableModule,
InitializableReentrancyGuard
{
using SafeMath for uint256;
using StableMath for uint256;
using SafeERC20 for IERC20;
// Events for Basket composition changes
event BassetAdded(address indexed bAsset, address integrator);
event BassetRemoved(address indexed bAsset);
event BasketWeightsUpdated(address[] bAssets, uint256[] maxWeights);
event BassetStatusChanged(address indexed bAsset, BassetStatus status);
event BasketStatusChanged();
event TransferFeeEnabled(address indexed bAsset, bool enabled);
// mAsset linked to the manager (const)
address public mAsset;
// Struct holding Basket details
Basket public basket;
// Mapping holds bAsset token address => array index
mapping(address => uint8) private bAssetsMap;
// Holds relative addresses of the integration platforms
address[] public integrations;
/**
* @dev Initialization function for upgradable proxy contract.
* This function should be called via Proxy just after contract deployment.
* @param _nexus Address of system Nexus
* @param _mAsset Address of the mAsset whose Basket to manage
* @param _bAssets Array of erc20 bAsset addresses
* @param _integrators Matching array of the platform intergations for bAssets
* @param _weights Weightings of each bAsset, summing to 1e18
* @param _hasTransferFees Bool signifying if this bAsset has xfer fees
*/
function initialize(
address _nexus,
address _mAsset,
address[] calldata _bAssets,
address[] calldata _integrators,
uint256[] calldata _weights,
bool[] calldata _hasTransferFees
)
external
initializer
{
InitializableReentrancyGuard._initialize();
InitializablePausableModule._initialize(_nexus);
require(_mAsset != address(0), "mAsset address is zero");
require(_bAssets.length > 0, "Must initialise with some bAssets");
mAsset = _mAsset;
// Defaults
basket.maxBassets = 10; // 10
basket.collateralisationRatio = 1e18; // 100%
for (uint256 i = 0; i < _bAssets.length; i++) {
_addBasset(
_bAssets[i],
_integrators[i],
StableMath.getRatioScale(),
_hasTransferFees[i]
);
}
_setBasketWeights(_bAssets, _weights, true);
}
/**
* @dev Requires the overall basket composition to be healthy
*/
modifier whenBasketIsHealthy() {
require(!basket.failed, "Basket must be alive");
_;
}
/**
* @dev Requires the overall basket composition to be healthy
*/
modifier whenNotRecolling() {
require(!basket.undergoingRecol, "No bAssets can be undergoing recol");
_;
}
/**
* @dev Verifies that the caller either Manager or Gov
*/
modifier managerOrGovernor() {
require(
_manager() == msg.sender || _governor() == msg.sender,
"Must be manager or governor");
_;
}
/**
* @dev Verifies that the caller is governed mAsset
*/
modifier onlyMasset() {
require(mAsset == msg.sender, "Must be called by mAsset");
_;
}
/***************************************
VAULT BALANCE
****************************************/
/**
* @dev Called by only mAsset, and only when the basket is healthy, to add units to
* storage after they have been deposited into the vault
* @param _bAssetIndex Index of the bAsset
* @param _increaseAmount Units deposited
*/
function increaseVaultBalance(
uint8 _bAssetIndex,
address /* _integrator */,
uint256 _increaseAmount
)
external
onlyMasset
whenBasketIsHealthy
nonReentrant
{
basket.bassets[_bAssetIndex].vaultBalance =
basket.bassets[_bAssetIndex].vaultBalance.add(_increaseAmount);
}
/**
* @dev Called by only mAsset, and only when the basket is healthy, to add units to
* storage after they have been deposited into the vault
* @param _bAssetIndices Array of bAsset indexes
* @param _increaseAmount Units deposited
*/
function increaseVaultBalances(
uint8[] calldata _bAssetIndices,
address[] calldata /* _integrator */,
uint256[] calldata _increaseAmount
)
external
onlyMasset
whenBasketIsHealthy
nonReentrant
{
uint256 len = _bAssetIndices.length;
for(uint i = 0; i < len; i++) {
basket.bassets[_bAssetIndices[i]].vaultBalance =
basket.bassets[_bAssetIndices[i]].vaultBalance.add(_increaseAmount[i]);
}
}
/**
* @dev Called by mAsset after redeeming tokens. Simply reduce the balance in the vault
* @param _bAssetIndex Index of the bAsset
* @param _decreaseAmount Units withdrawn
*/
function decreaseVaultBalance(
uint8 _bAssetIndex,
address /* _integrator */,
uint256 _decreaseAmount
)
external
onlyMasset
nonReentrant
{
basket.bassets[_bAssetIndex].vaultBalance =
basket.bassets[_bAssetIndex].vaultBalance.sub(_decreaseAmount);
}
/**
* @dev Called by mAsset after redeeming tokens. Simply reduce the balance in the vault
* @param _bAssetIndices Array of bAsset indexes
* @param _decreaseAmount Units withdrawn
*/
function decreaseVaultBalances(
uint8[] calldata _bAssetIndices,
address[] calldata /* _integrator */,
uint256[] calldata _decreaseAmount
)
external
onlyMasset
nonReentrant
{
uint256 len = _bAssetIndices.length;
for(uint i = 0; i < len; i++) {
basket.bassets[_bAssetIndices[i]].vaultBalance =
basket.bassets[_bAssetIndices[i]].vaultBalance.sub(_decreaseAmount[i]);
}
}
/**
* @dev Called by mAsset to calculate how much interest has been generated in the basket
* and withdraw it. Cycles through the connected platforms to check the balances.
* @return interestCollected Total amount of interest collected, in mAsset terms
* @return gains Array of bAsset units gained
*/
function collectInterest()
external
onlyMasset
whenNotPaused
whenBasketIsHealthy
nonReentrant
returns (uint256 interestCollected, uint256[] memory gains)
{
// Get basket details
(Basset[] memory allBassets, uint256 count) = _getBassets();
gains = new uint256[](count);
interestCollected = 0;
// foreach bAsset
for(uint8 i = 0; i < count; i++) {
Basset memory b = allBassets[i];
// call each integration to `checkBalance`
uint256 balance = IPlatformIntegration(integrations[i]).checkBalance(b.addr);
uint256 oldVaultBalance = b.vaultBalance;
// accumulate interest (ratioed bAsset)
if(balance > oldVaultBalance && b.status == BassetStatus.Normal) {
// Update balance
basket.bassets[i].vaultBalance = balance;
uint256 interestDelta = balance.sub(oldVaultBalance);
gains[i] = interestDelta;
// Calc MassetQ
uint256 ratioedDelta = interestDelta.mulRatioTruncate(b.ratio);
interestCollected = interestCollected.add(ratioedDelta);
} else {
gains[i] = 0;
}
}
}
/***************************************
BASKET MANAGEMENT
****************************************/
/**
* @dev External func to allow the Governor to conduct add operations on the Basket
* @param _bAsset Address of the ERC20 token to add to the Basket
* @param _integration Address of the vault integration to deposit and withdraw
* @param _isTransferFeeCharged Bool - are transfer fees charged on this bAsset
* @return index Position of the bAsset in the Basket
*/
function addBasset(address _bAsset, address _integration, bool _isTransferFeeCharged)
external
onlyGovernor
whenBasketIsHealthy
whenNotRecolling
returns (uint8 index)
{
index = _addBasset(
_bAsset,
_integration,
StableMath.getRatioScale(),
_isTransferFeeCharged
);
}
/**
* @dev Adds a bAsset to the Basket, fetching its decimals and calculating the Ratios
* @param _bAsset Address of the ERC20 token to add to the Basket
* @param _integration Address of the Platform Integration
* @param _measurementMultiple Base 1e8 var to determine measurement ratio
* between bAsset:mAsset
* @param _isTransferFeeCharged Bool - are transfer fees charged on this bAsset
* @return index Position of the bAsset in the Basket
*/
function _addBasset(
address _bAsset,
address _integration,
uint256 _measurementMultiple,
bool _isTransferFeeCharged
)
internal
returns (uint8 index)
{
require(_bAsset != address(0), "bAsset address must be valid");
require(_integration != address(0), "Integration address must be valid");
require(_measurementMultiple >= 1e6 && _measurementMultiple <= 1e10, "MM out of range");
(bool alreadyInBasket, ) = _isAssetInBasket(_bAsset);
require(!alreadyInBasket, "bAsset already exists in Basket");
// Should fail if bAsset is not added to integration
// Programmatic enforcement of bAsset validity should service through decentralised feed
IPlatformIntegration(_integration).checkBalance(_bAsset);
uint256 bAsset_decimals = CommonHelpers.getDecimals(_bAsset);
uint256 delta = uint256(18).sub(bAsset_decimals);
uint256 ratio = _measurementMultiple.mul(10 ** delta);
uint8 numberOfBassetsInBasket = uint8(basket.bassets.length);
require(numberOfBassetsInBasket < basket.maxBassets, "Max bAssets in Basket");
bAssetsMap[_bAsset] = numberOfBassetsInBasket;
integrations.push(_integration);
basket.bassets.push(Basset({
addr: _bAsset,
ratio: ratio,
maxWeight: 0,
vaultBalance: 0,
status: BassetStatus.Normal,
isTransferFeeCharged: _isTransferFeeCharged
}));
emit BassetAdded(_bAsset, _integration);
index = numberOfBassetsInBasket;
}
/**
* @dev External call for the governor to set weightings of all bAssets
* @param _bAssets Array of bAsset addresses
* @param _weights Array of bAsset weights - summing 100% where 100% == 1e18
*/
function setBasketWeights(
address[] calldata _bAssets,
uint256[] calldata _weights
)
external
onlyGovernor
whenBasketIsHealthy
{
_setBasketWeights(_bAssets, _weights, false);
}
/**
* @notice Sets new Basket weightings
* @dev Requires the modified bAssets to be in a Normal state
* @param _bAssets Array of bAsset addresses
* @param _weights Array of bAsset weights - summing 100% where 100% == 1e18
* @param _isBootstrap True only on the first occurence of weight setting
*/
function _setBasketWeights(
address[] memory _bAssets,
uint256[] memory _weights,
bool _isBootstrap
)
internal
{
uint256 bAssetCount = _bAssets.length;
require(bAssetCount > 0, "Empty bAssets array passed");
require(bAssetCount == _weights.length, "Must be matching bAsset arrays");
for (uint256 i = 0; i < bAssetCount; i++) {
(bool exists, uint8 index) = _isAssetInBasket(_bAssets[i]);
require(exists, "bAsset must exist");
Basset memory bAsset = _getBasset(index);
uint256 bAssetWeight = _weights[i];
if(bAsset.status == BassetStatus.Normal) {
require(
bAssetWeight <= 1e18,
"Asset weight must be <= 100%"
);
basket.bassets[index].maxWeight = bAssetWeight;
} else {
require(
bAssetWeight == basket.bassets[index].maxWeight,
"Affected bAssets must be static"
);
}
}
if(!_isBootstrap){
_validateBasketWeight();
}
emit BasketWeightsUpdated(_bAssets, _weights);
}
/**
* @dev Throws if the total Basket weight does not sum to 100
*/
function _validateBasketWeight() internal view {
uint256 len = basket.bassets.length;
uint256 weightSum = 0;
for(uint256 i = 0; i < len; i++) {
weightSum = weightSum.add(basket.bassets[i].maxWeight);
}
require(weightSum >= 1e18 && weightSum <= 4e18, "Basket weight must be >= 100 && <= 400%");
}
/**
* @dev Update transfer fee flag for a given bAsset, should it change its fee practice
* @param _bAsset bAsset address
* @param _flag Charge transfer fee when its set to 'true', otherwise 'false'
*/
function setTransferFeesFlag(address _bAsset, bool _flag)
external
managerOrGovernor
{
(bool exist, uint8 index) = _isAssetInBasket(_bAsset);
require(exist, "bAsset does not exist");
basket.bassets[index].isTransferFeeCharged = _flag;
emit TransferFeeEnabled(_bAsset, _flag);
}
/**
* @dev Removes a specific Asset from the Basket, given that its target/collateral
* level is already 0, throws if invalid.
* @param _assetToRemove The asset to remove from the basket
*/
function removeBasset(address _assetToRemove)
external
whenBasketIsHealthy
whenNotRecolling
managerOrGovernor
{
_removeBasset(_assetToRemove);
}
/**
* @dev Removes a specific Asset from the Basket, given that its target/collateral
* level is already 0, throws if invalid.
* @param _assetToRemove The asset to remove from the basket
*/
function _removeBasset(address _assetToRemove) internal {
(bool exists, uint8 index) = _isAssetInBasket(_assetToRemove);
require(exists, "bAsset does not exist");
uint256 len = basket.bassets.length;
Basset memory bAsset = basket.bassets[index];
require(bAsset.maxWeight == 0, "bAsset must have a target weight of 0");
require(bAsset.vaultBalance == 0, "bAsset vault must be empty");
require(bAsset.status != BassetStatus.Liquidating, "bAsset must be active");
uint8 lastIndex = uint8(len.sub(1));
if(index == lastIndex) {
basket.bassets.pop();
bAssetsMap[_assetToRemove] = 0;
integrations.pop();
} else {
// Swap the bassets
basket.bassets[index] = basket.bassets[lastIndex];
basket.bassets.pop();
Basset memory swappedBasset = basket.bassets[index];
// Update bassetsMap
bAssetsMap[_assetToRemove] = 0;
bAssetsMap[swappedBasset.addr] = index;
// Update integrations
integrations[index] = integrations[lastIndex];
integrations.pop();
}
emit BassetRemoved(bAsset.addr);
}
/***************************************
GETTERS
****************************************/
/**
* @dev Get basket details for `MassetStructs.Basket`
* @return b Basket struct
*/
function getBasket()
external
view
returns (Basket memory b)
{
b = basket;
}
/**
* @dev Prepare given bAsset for Forging. Currently returns integrator
* and essential minting info.
* @param _bAsset Address of the bAsset
* @return props Struct of all relevant Forge information
*/
function prepareForgeBasset(address _bAsset, uint256 /*_amt*/, bool /*_mint*/)
external
whenNotPaused
whenNotRecolling
returns (bool isValid, BassetDetails memory bInfo)
{
(bool exists, uint8 idx) = _isAssetInBasket(_bAsset);
require(exists, "bAsset does not exist");
isValid = true;
bInfo = BassetDetails({
bAsset: basket.bassets[idx],
integrator: integrations[idx],
index: idx
});
}
/**
* @dev Prepare given bAssets for swapping
* @param _input Address of the input bAsset
* @param _output Address of the output bAsset
* @param _isMint Is this swap actually a mint? i.e. output == address(mAsset)
* @return props Struct of all relevant Forge information
*/
function prepareSwapBassets(address _input, address _output, bool _isMint)
external
view
whenNotPaused
returns (bool, string memory, BassetDetails memory, BassetDetails memory)
{
BassetDetails memory input = BassetDetails({
bAsset: basket.bassets[0],
integrator: address(0),
index: 0
});
BassetDetails memory output = input;
// Check that basket state is healthy
if(basket.failed || basket.undergoingRecol){
return (false, "Basket is undergoing change", input, output);
}
// Fetch input bAsset
(bool inputExists, uint8 inputIdx) = _isAssetInBasket(_input);
if(!inputExists) {
return (false, "Input asset does not exist", input, output);
}
input = BassetDetails({
bAsset: basket.bassets[inputIdx],
integrator: integrations[inputIdx],
index: inputIdx
});
// If this is a mint, we don't need output bAsset
if(_isMint) {
return (true, "", input, output);
}
// Fetch output bAsset
(bool outputExists, uint8 outputIdx) = _isAssetInBasket(_output);
if(!outputExists) {
return (false, "Output asset does not exist", input, output);
}
output = BassetDetails({
bAsset: basket.bassets[outputIdx],
integrator: integrations[outputIdx],
index: outputIdx
});
return (true, "", input, output);
}
/**
* @dev Prepare given bAsset addresses for Forging. Currently returns integrator
* and essential minting info for each bAsset
* @param _bAssets Array of bAsset addresses with which to forge
* @return props Struct of all relevant Forge information
*/
function prepareForgeBassets(
address[] calldata _bAssets,
uint256[] calldata /*_amts*/,
bool /* _isMint */
)
external
whenNotPaused
whenNotRecolling
returns (ForgePropsMulti memory props)
{
// Pass the fetching logic to the internal view func to reduce SLOAD cost
(Basset[] memory bAssets, uint8[] memory indexes, address[] memory integrators) = _fetchForgeBassets(_bAssets);
props = ForgePropsMulti({
isValid: true,
bAssets: bAssets,
integrators: integrators,
indexes: indexes
});
}
/**
* @dev Prepare given bAsset addresses for RedeemMulti. Currently returns integrator
* and essential minting info for each bAsset
* @return props Struct of all relevant Forge information
*/
function prepareRedeemMulti()
external
view
whenNotPaused
whenNotRecolling
returns (RedeemPropsMulti memory props)
{
(Basset[] memory bAssets, uint256 len) = _getBassets();
address[] memory orderedIntegrators = new address[](len);
uint8[] memory indexes = new uint8[](len);
for(uint8 i = 0; i < len; i++){
orderedIntegrators[i] = integrations[i];
indexes[i] = i;
}
props = RedeemPropsMulti({
colRatio: basket.collateralisationRatio,
bAssets: bAssets,
integrators: orderedIntegrators,
indexes: indexes
});
}
/**
* @dev Internal func to fetch an array of bAssets and their integrators from storage
* @param _bAssets Array of non-duplicate bAsset addresses with which to forge
* @return bAssets Array of bAsset structs for the given addresses
* @return indexes Array of indexes for the given addresses
* @return integrators Array of integrators for the given addresses
*/
function _fetchForgeBassets(address[] memory _bAssets)
internal
view
returns (
Basset[] memory bAssets,
uint8[] memory indexes,
address[] memory integrators
)
{
uint8 len = uint8(_bAssets.length);
bAssets = new Basset[](len);
integrators = new address[](len);
indexes = new uint8[](len);
// Iterate through the input
for(uint8 i = 0; i < len; i++) {
address current = _bAssets[i];
// If there is a duplicate here, throw
// Gas costs do not incur SLOAD
for(uint8 j = i+1; j < len; j++){
require(current != _bAssets[j], "Must have no duplicates");
}
// Fetch and log all the relevant data
(bool exists, uint8 index) = _isAssetInBasket(current);
require(exists, "bAsset must exist");
indexes[i] = index;
bAssets[i] = basket.bassets[index];
integrators[i] = integrations[index];
}
}
/**
* @dev Get data for a all bAssets in basket
* @return bAssets Struct[] with full bAsset data
* @return len Number of bAssets in the Basket
*/
function getBassets()
external
view
returns (
Basset[] memory bAssets,
uint256 len
)
{
return _getBassets();
}
/**
* @dev Get data for a specific bAsset, if it exists
* @param _bAsset Address of bAsset
* @return bAsset Struct with full bAsset data
*/
function getBasset(address _bAsset)
external
view
returns (Basset memory bAsset)
{
(bool exists, uint8 index) = _isAssetInBasket(_bAsset);
require(exists, "bAsset must exist");
bAsset = _getBasset(index);
}
/**
* @dev Get current integrator for a specific bAsset, if it exists
* @param _bAsset Address of bAsset
* @return integrator Address of current integrator
*/
function getBassetIntegrator(address _bAsset)
external
view
returns (address integrator)
{
(bool exists, uint8 index) = _isAssetInBasket(_bAsset);
require(exists, "bAsset must exist");
integrator = integrations[index];
}
function _getBassets()
internal
view
returns (
Basset[] memory bAssets,
uint256 len
)
{
bAssets = basket.bassets;
len = basket.bassets.length;
}
function _getBasset(uint8 _bAssetIndex)
internal
view
returns (Basset memory bAsset)
{
bAsset = basket.bassets[_bAssetIndex];
}
/***************************************
HELPERS
****************************************/
/**
* @dev Checks if a particular asset is in the basket
* @param _asset Address of bAsset to look for
* @return exists bool to signal that the asset is in basket
* @return index uint256 Index of the bAsset
*/
function _isAssetInBasket(address _asset)
internal
view
returns (bool exists, uint8 index)
{
index = bAssetsMap[_asset];
if(index == 0) {
if(basket.bassets.length == 0) {
return (false, 0);
}
return (basket.bassets[0].addr == _asset, 0);
}
return (true, index);
}
/**
* @notice Determine whether or not a bAsset has already undergone re-collateralisation
* @param _status Status of the bAsset
* @return Bool to determine if undergone re-collateralisation
*/
function _bAssetHasRecolled(BassetStatus _status)
internal
pure
returns (bool)
{
if(_status == BassetStatus.Liquidating ||
_status == BassetStatus.Liquidated ||
_status == BassetStatus.Failed) {
return true;
}
return false;
}
/***************************************
RE-COLLATERALISATION
****************************************/
/**
* @dev Executes the Auto Redistribution event by isolating the bAsset from the Basket
* @param _bAsset Address of the ERC20 token to isolate
* @param _belowPeg Bool to describe whether the bAsset deviated below peg (t)
* or above (f)
* @return alreadyActioned Bool to show whether a bAsset had already been actioned
*/
function handlePegLoss(address _bAsset, bool _belowPeg)
external
managerOrGovernor
whenBasketIsHealthy
returns (bool alreadyActioned)
{
(bool exists, uint256 i) = _isAssetInBasket(_bAsset);
require(exists, "bAsset must exist in Basket");
BassetStatus oldStatus = basket.bassets[i].status;
BassetStatus newStatus =
_belowPeg ? BassetStatus.BrokenBelowPeg : BassetStatus.BrokenAbovePeg;
if(oldStatus == newStatus || _bAssetHasRecolled(oldStatus)) {
return true;
}
// If we need to update the status.. then do it
basket.bassets[i].status = newStatus;
emit BassetStatusChanged(_bAsset, newStatus);
return false;
}
/**
* @dev Negates the isolation of a given bAsset
* @param _bAsset Address of the bAsset
*/
function negateIsolation(address _bAsset)
external
managerOrGovernor
{
(bool exists, uint256 i) = _isAssetInBasket(_bAsset);
require(exists, "bAsset must exist");
BassetStatus currentStatus = basket.bassets[i].status;
if(currentStatus == BassetStatus.BrokenBelowPeg ||
currentStatus == BassetStatus.BrokenAbovePeg ||
currentStatus == BassetStatus.Blacklisted) {
basket.bassets[i].status = BassetStatus.Normal;
emit BassetStatusChanged(_bAsset, BassetStatus.Normal);
}
}
/**
* @dev Marks a bAsset as permanently deviated from peg
* @param _bAsset Address of the bAsset
*/
function failBasset(address _bAsset)
external
onlyGovernor
{
(bool exists, uint256 i) = _isAssetInBasket(_bAsset);
require(exists, "bAsset must exist");
BassetStatus currentStatus = basket.bassets[i].status;
require(
currentStatus == BassetStatus.BrokenBelowPeg ||
currentStatus == BassetStatus.BrokenAbovePeg,
"bAsset must be affected"
);
basket.failed = true;
}
}File 6 of 7: ForgeValidator
pragma solidity 0.5.16;
pragma experimental ABIEncoderV2;
interface MassetStructs {
/** @dev Stores high level basket info */
struct Basket {
/** @dev Array of Bassets currently active */
Basset[] bassets;
/** @dev Max number of bAssets that can be present in any Basket */
uint8 maxBassets;
/** @dev Some bAsset is undergoing re-collateralisation */
bool undergoingRecol;
/**
* @dev In the event that we do not raise enough funds from the auctioning of a failed Basset,
* The Basket is deemed as failed, and is undercollateralised to a certain degree.
* The collateralisation ratio is used to calc Masset burn rate.
*/
bool failed;
uint256 collateralisationRatio;
}
/** @dev Stores bAsset info. The struct takes 5 storage slots per Basset */
struct Basset {
/** @dev Address of the bAsset */
address addr;
/** @dev Status of the basset, */
BassetStatus status; // takes uint8 datatype (1 byte) in storage
/** @dev An ERC20 can charge transfer fee, for example USDT, DGX tokens. */
bool isTransferFeeCharged; // takes a byte in storage
/**
* @dev 1 Basset * ratio / ratioScale == x Masset (relative value)
* If ratio == 10e8 then 1 bAsset = 10 mAssets
* A ratio is divised as 10^(18-tokenDecimals) * measurementMultiple(relative value of 1 base unit)
*/
uint256 ratio;
/** @dev Target weights of the Basset (100% == 1e18) */
uint256 maxWeight;
/** @dev Amount of the Basset that is held in Collateral */
uint256 vaultBalance;
}
/** @dev Status of the Basset - has it broken its peg? */
enum BassetStatus {
Default,
Normal,
BrokenBelowPeg,
BrokenAbovePeg,
Blacklisted,
Liquidating,
Liquidated,
Failed
}
/** @dev Internal details on Basset */
struct BassetDetails {
Basset bAsset;
address integrator;
uint8 index;
}
/** @dev All details needed to Forge with multiple bAssets */
struct ForgePropsMulti {
bool isValid; // Flag to signify that forge bAssets have passed validity check
Basset[] bAssets;
address[] integrators;
uint8[] indexes;
}
/** @dev All details needed for proportionate Redemption */
struct RedeemPropsMulti {
uint256 colRatio;
Basset[] bAssets;
address[] integrators;
uint8[] indexes;
}
}
contract IForgeValidator is MassetStructs {
function validateMint(uint256 _totalVault, Basset calldata _basset, uint256 _bAssetQuantity)
external pure returns (bool, string memory);
function validateMintMulti(uint256 _totalVault, Basset[] calldata _bassets, uint256[] calldata _bAssetQuantities)
external pure returns (bool, string memory);
function validateSwap(uint256 _totalVault, Basset calldata _inputBasset, Basset calldata _outputBasset, uint256 _quantity)
external pure returns (bool, string memory, uint256, bool);
function validateRedemption(
bool basketIsFailed,
uint256 _totalVault,
Basset[] calldata _allBassets,
uint8[] calldata _indices,
uint256[] calldata _bassetQuantities) external pure returns (bool, string memory, bool);
function calculateRedemptionMulti(
uint256 _mAssetQuantity,
Basset[] calldata _allBassets) external pure returns (bool, string memory, uint256[] memory);
}
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*
* _Available since v2.4.0._
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*
* _Available since v2.4.0._
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*
* _Available since v2.4.0._
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
library StableMath {
using SafeMath for uint256;
/**
* @dev Scaling unit for use in specific calculations,
* where 1 * 10**18, or 1e18 represents a unit '1'
*/
uint256 private constant FULL_SCALE = 1e18;
/**
* @notice Token Ratios are used when converting between units of bAsset, mAsset and MTA
* Reasoning: Takes into account token decimals, and difference in base unit (i.e. grams to Troy oz for gold)
* @dev bAsset ratio unit for use in exact calculations,
* where (1 bAsset unit * bAsset.ratio) / ratioScale == x mAsset unit
*/
uint256 private constant RATIO_SCALE = 1e8;
/**
* @dev Provides an interface to the scaling unit
* @return Scaling unit (1e18 or 1 * 10**18)
*/
function getFullScale() internal pure returns (uint256) {
return FULL_SCALE;
}
/**
* @dev Provides an interface to the ratio unit
* @return Ratio scale unit (1e8 or 1 * 10**8)
*/
function getRatioScale() internal pure returns (uint256) {
return RATIO_SCALE;
}
/**
* @dev Scales a given integer to the power of the full scale.
* @param x Simple uint256 to scale
* @return Scaled value a to an exact number
*/
function scaleInteger(uint256 x)
internal
pure
returns (uint256)
{
return x.mul(FULL_SCALE);
}
/***************************************
PRECISE ARITHMETIC
****************************************/
/**
* @dev Multiplies two precise units, and then truncates by the full scale
* @param x Left hand input to multiplication
* @param y Right hand input to multiplication
* @return Result after multiplying the two inputs and then dividing by the shared
* scale unit
*/
function mulTruncate(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
return mulTruncateScale(x, y, FULL_SCALE);
}
/**
* @dev Multiplies two precise units, and then truncates by the given scale. For example,
* when calculating 90% of 10e18, (10e18 * 9e17) / 1e18 = (9e36) / 1e18 = 9e18
* @param x Left hand input to multiplication
* @param y Right hand input to multiplication
* @param scale Scale unit
* @return Result after multiplying the two inputs and then dividing by the shared
* scale unit
*/
function mulTruncateScale(uint256 x, uint256 y, uint256 scale)
internal
pure
returns (uint256)
{
// e.g. assume scale = fullScale
// z = 10e18 * 9e17 = 9e36
uint256 z = x.mul(y);
// return 9e38 / 1e18 = 9e18
return z.div(scale);
}
/**
* @dev Multiplies two precise units, and then truncates by the full scale, rounding up the result
* @param x Left hand input to multiplication
* @param y Right hand input to multiplication
* @return Result after multiplying the two inputs and then dividing by the shared
* scale unit, rounded up to the closest base unit.
*/
function mulTruncateCeil(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
// e.g. 8e17 * 17268172638 = 138145381104e17
uint256 scaled = x.mul(y);
// e.g. 138145381104e17 + 9.99...e17 = 138145381113.99...e17
uint256 ceil = scaled.add(FULL_SCALE.sub(1));
// e.g. 13814538111.399...e18 / 1e18 = 13814538111
return ceil.div(FULL_SCALE);
}
/**
* @dev Precisely divides two units, by first scaling the left hand operand. Useful
* for finding percentage weightings, i.e. 8e18/10e18 = 80% (or 8e17)
* @param x Left hand input to division
* @param y Right hand input to division
* @return Result after multiplying the left operand by the scale, and
* executing the division on the right hand input.
*/
function divPrecisely(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
// e.g. 8e18 * 1e18 = 8e36
uint256 z = x.mul(FULL_SCALE);
// e.g. 8e36 / 10e18 = 8e17
return z.div(y);
}
/***************************************
RATIO FUNCS
****************************************/
/**
* @dev Multiplies and truncates a token ratio, essentially flooring the result
* i.e. How much mAsset is this bAsset worth?
* @param x Left hand operand to multiplication (i.e Exact quantity)
* @param ratio bAsset ratio
* @return Result after multiplying the two inputs and then dividing by the ratio scale
*/
function mulRatioTruncate(uint256 x, uint256 ratio)
internal
pure
returns (uint256 c)
{
return mulTruncateScale(x, ratio, RATIO_SCALE);
}
/**
* @dev Multiplies and truncates a token ratio, rounding up the result
* i.e. How much mAsset is this bAsset worth?
* @param x Left hand input to multiplication (i.e Exact quantity)
* @param ratio bAsset ratio
* @return Result after multiplying the two inputs and then dividing by the shared
* ratio scale, rounded up to the closest base unit.
*/
function mulRatioTruncateCeil(uint256 x, uint256 ratio)
internal
pure
returns (uint256)
{
// e.g. How much mAsset should I burn for this bAsset (x)?
// 1e18 * 1e8 = 1e26
uint256 scaled = x.mul(ratio);
// 1e26 + 9.99e7 = 100..00.999e8
uint256 ceil = scaled.add(RATIO_SCALE.sub(1));
// return 100..00.999e8 / 1e8 = 1e18
return ceil.div(RATIO_SCALE);
}
/**
* @dev Precisely divides two ratioed units, by first scaling the left hand operand
* i.e. How much bAsset is this mAsset worth?
* @param x Left hand operand in division
* @param ratio bAsset ratio
* @return Result after multiplying the left operand by the scale, and
* executing the division on the right hand input.
*/
function divRatioPrecisely(uint256 x, uint256 ratio)
internal
pure
returns (uint256 c)
{
// e.g. 1e14 * 1e8 = 1e22
uint256 y = x.mul(RATIO_SCALE);
// return 1e22 / 1e12 = 1e10
return y.div(ratio);
}
/***************************************
HELPERS
****************************************/
/**
* @dev Calculates minimum of two numbers
* @param x Left hand input
* @param y Right hand input
* @return Minimum of the two inputs
*/
function min(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
return x > y ? y : x;
}
/**
* @dev Calculated maximum of two numbers
* @param x Left hand input
* @param y Right hand input
* @return Maximum of the two inputs
*/
function max(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
return x > y ? x : y;
}
/**
* @dev Clamps a value to an upper bound
* @param x Left hand input
* @param upperBound Maximum possible value to return
* @return Input x clamped to a maximum value, upperBound
*/
function clamp(uint256 x, uint256 upperBound)
internal
pure
returns (uint256)
{
return x > upperBound ? upperBound : x;
}
}
/**
* @title ForgeValidator
* @author Stability Labs Pty. Ltd.
* @notice Calculates whether or not minting or redemption is valid, based
* on how it affects the underlying basket collateral weightings
* @dev VERSION: 1.1
* DATE: 2020-06-22
*/
contract ForgeValidator is IForgeValidator {
using SafeMath for uint256;
using StableMath for uint256;
/***************************************
MINT
****************************************/
/**
* @notice Checks whether a given mint is valid and returns the result
* @dev Is the resulting weighting of the max bAsset beyond it's implicit max weight?
* Max weight is determined as max weight (in units)
* @param _totalVault Current sum of basket collateral
* @param _bAsset Struct containing relevant data on the bAsset
* @param _bAssetQuantity Number of bAsset units that will be used to mint
* @return isValid Bool to signify that the mint does not move our weightings the wrong way
* @return reason If the mint is invalid, this is the reason
*/
function validateMint(
uint256 _totalVault,
Basset calldata _bAsset,
uint256 _bAssetQuantity
)
external
pure
returns (bool isValid, string memory reason)
{
if(
_bAsset.status == BassetStatus.BrokenBelowPeg ||
_bAsset.status == BassetStatus.Liquidating ||
_bAsset.status == BassetStatus.Blacklisted
) {
return (false, "bAsset not allowed in mint");
}
// How much mAsset is this _bAssetQuantity worth?
uint256 mintAmountInMasset = _bAssetQuantity.mulRatioTruncate(_bAsset.ratio);
// How much of this bAsset do we have in the vault, in terms of mAsset?
uint256 newBalanceInMasset = _bAsset.vaultBalance.mulRatioTruncate(_bAsset.ratio).add(mintAmountInMasset);
// What is the max weight of this bAsset in the basket?
uint256 maxWeightInUnits = (_totalVault.add(mintAmountInMasset)).mulTruncate(_bAsset.maxWeight);
if(newBalanceInMasset > maxWeightInUnits) {
return (false, "bAssets used in mint cannot exceed their max weight");
}
return (true, "");
}
/**
* @notice Checks whether a given mint using more than one asset is valid and returns the result
* @dev Is the resulting weighting of the max bAssets beyond their implicit max weight?
* Max weight is determined as max weight (in units)
* @param _totalVault Current sum of basket collateral
* @param _bAssets Array of Struct containing relevant data on the bAssets
* @param _bAssetQuantities Number of bAsset units that will be used to mint (aligned with above)
* @return isValid Bool to signify that the mint does not move our weightings the wrong way
* @return reason If the mint is invalid, this is the reason
*/
function validateMintMulti(
uint256 _totalVault,
Basset[] calldata _bAssets,
uint256[] calldata _bAssetQuantities
)
external
pure
returns (bool isValid, string memory reason)
{
uint256 bAssetCount = _bAssets.length;
if(bAssetCount != _bAssetQuantities.length) return (false, "Input length should be equal");
uint256[] memory newBalances = new uint256[](bAssetCount);
uint256 newTotalVault = _totalVault;
// Theoretically add the mint quantities to the vault
for(uint256 j = 0; j < bAssetCount; j++){
Basset memory b = _bAssets[j];
BassetStatus bAssetStatus = b.status;
if(
bAssetStatus == BassetStatus.BrokenBelowPeg ||
bAssetStatus == BassetStatus.Liquidating ||
bAssetStatus == BassetStatus.Blacklisted
) {
return (false, "bAsset not allowed in mint");
}
// How much mAsset is this bassetquantity worth?
uint256 mintAmountInMasset = _bAssetQuantities[j].mulRatioTruncate(b.ratio);
// How much of this bAsset do we have in the vault, in terms of mAsset?
newBalances[j] = b.vaultBalance.mulRatioTruncate(b.ratio).add(mintAmountInMasset);
newTotalVault = newTotalVault.add(mintAmountInMasset);
}
for(uint256 k = 0; k < bAssetCount; k++){
// What is the max weight of this bAsset in the basket?
uint256 maxWeightInUnits = newTotalVault.mulTruncate(_bAssets[k].maxWeight);
if(newBalances[k] > maxWeightInUnits) {
return (false, "bAssets used in mint cannot exceed their max weight");
}
}
return (true, "");
}
/***************************************
SWAP
****************************************/
/**
* @notice Checks whether a given swap is valid and calculates the output
* @dev Input bAsset must not exceed max weight, output bAsset must have sufficient liquidity
* @param _totalVault Current sum of basket collateral
* @param _inputBasset Input bAsset details
* @param _outputBasset Output bAsset details
* @param _quantity Number of bAsset units on the input side
* @return isValid Bool to signify that the mint does not move our weightings the wrong way
* @return reason If the swap is invalid, this is the reason
* @return output Units of output bAsset, before fee is applied
* @return applySwapFee Bool to signify that the swap fee is applied
*/
function validateSwap(
uint256 _totalVault,
Basset calldata _inputBasset,
Basset calldata _outputBasset,
uint256 _quantity
)
external
pure
returns (bool isValid, string memory reason, uint256 output, bool applySwapFee)
{
if(_inputBasset.status != BassetStatus.Normal || _outputBasset.status != BassetStatus.Normal) {
return (false, "bAsset not allowed in swap", 0, false);
}
// How much mAsset is this _bAssetQuantity worth?
uint256 inputAmountInMasset = _quantity.mulRatioTruncate(_inputBasset.ratio);
// 1. Determine output bAsset valid
// - Enough units in the bank
uint256 outputAmount = inputAmountInMasset.divRatioPrecisely(_outputBasset.ratio);
if(outputAmount > _outputBasset.vaultBalance) {
return (false, "Not enough liquidity", 0, false);
}
// 1.1. If it is currently overweight, then no fee
applySwapFee = true;
uint256 outputBalanceMasset = _outputBasset.vaultBalance.mulRatioTruncate(_outputBasset.ratio);
uint256 outputMaxWeightUnits = _totalVault.mulTruncate(_outputBasset.maxWeight);
if(outputBalanceMasset > outputMaxWeightUnits) {
applySwapFee = false;
}
// 2. Calculate input bAsset valid - If incoming basket goes above weight, then fail
// How much of this bAsset do we have in the vault, in terms of mAsset?
uint256 newInputBalanceInMasset = _inputBasset.vaultBalance.mulRatioTruncate(_inputBasset.ratio).add(inputAmountInMasset);
// What is the max weight of this bAsset in the basket?
uint256 inputMaxWeightInUnits = _totalVault.mulTruncate(_inputBasset.maxWeight);
if(newInputBalanceInMasset > inputMaxWeightInUnits) {
return (false, "Input must remain below max weighting", 0, false);
}
// 3. Return swap output
return (true, "", outputAmount, applySwapFee);
}
/***************************************
REDEEM
****************************************/
/**
* @notice Checks whether a given redemption is valid and returns the result
* @dev A redemption is valid if it does not push any untouched bAssets above their
* max weightings. In addition, if bAssets are currently above their max weight
* (i.e. during basket composition changes) they must be redeemed
* @param _basketIsFailed Bool to suggest that the basket has failed a recollateralisation attempt
* @param _totalVault Sum of collateral units in the basket
* @param _allBassets Array of all bAsset information
* @param _indices Indexes of the bAssets to redeem
* @param _bAssetQuantities Quantity of bAsset to redeem
* @return isValid Bool to signify that the redemption is allowed
* @return reason If the redemption is invalid, this is the reason
* @return feeRequired Does this redemption require the swap fee to be applied
*/
function validateRedemption(
bool _basketIsFailed,
uint256 _totalVault,
Basset[] calldata _allBassets,
uint8[] calldata _indices,
uint256[] calldata _bAssetQuantities
)
external
pure
returns (bool, string memory, bool)
{
uint256 idxCount = _indices.length;
if(idxCount != _bAssetQuantities.length) return (false, "Input arrays must have equal length", false);
// Get current weightings, and cache some outputs from the loop to avoid unecessary recursion
BasketStateResponse memory data = _getBasketState(_totalVault, _allBassets);
if(!data.isValid) return (false, data.reason, false);
// If the basket is in an affected state, enforce proportional redemption
if(
_basketIsFailed ||
data.atLeastOneBroken
) {
return (false, "Must redeem proportionately", false);
} else if (data.overWeightCount > idxCount) {
return (false, "Redemption must contain all overweight bAssets", false);
}
uint256 newTotalVault = _totalVault;
// Simulate the redemption on the ratioedBassetVaults and totalSupply
for(uint256 i = 0; i < idxCount; i++){
uint8 idx = _indices[i];
if(idx >= _allBassets.length) return (false, "Basset does not exist", false);
Basset memory bAsset = _allBassets[idx];
uint256 quantity = _bAssetQuantities[i];
if(quantity > bAsset.vaultBalance) return (false, "Cannot redeem more bAssets than are in the vault", false);
// Calculate ratioed redemption amount in mAsset terms
uint256 ratioedRedemptionAmount = quantity.mulRatioTruncate(bAsset.ratio);
// Subtract ratioed redemption amount from both vault and total supply
data.ratioedBassetVaults[idx] = data.ratioedBassetVaults[idx].sub(ratioedRedemptionAmount);
newTotalVault = newTotalVault.sub(ratioedRedemptionAmount);
}
// Get overweight after
bool atLeastOneBecameOverweight =
_getOverweightBassetsAfter(newTotalVault, _allBassets, data.ratioedBassetVaults, data.isOverWeight);
bool applySwapFee = true;
// If there are any bAssets overweight, we must redeem them all
if(data.overWeightCount > 0) {
for(uint256 j = 0; j < idxCount; j++) {
if(!data.isOverWeight[_indices[j]]) return (false, "Must redeem overweight bAssets", false);
}
applySwapFee = false;
}
// Since no bAssets were overweight before, if one becomes overweight, throw
if(atLeastOneBecameOverweight) return (false, "bAssets must remain below max weight", false);
return (true, "", applySwapFee);
}
/**
* @notice Calculates the relative quantities of bAssets to redeem, with current basket state
* @dev Sum the value of the bAssets, and then find the proportions relative to the desired
* mAsset quantity.
* @param _mAssetQuantity Quantity of mAsset to redeem
* @param _allBassets Array of all bAsset information
* @return isValid Bool to signify that the redemption is allowed
* @return reason If the redemption is invalid, this is the reason
* @return quantities Array of bAsset quantities to redeem
*/
function calculateRedemptionMulti(
uint256 _mAssetQuantity,
Basset[] calldata _allBassets
)
external
pure
returns (bool, string memory, uint256[] memory)
{
// e.g. mAsset = 1e20 (100)
// e.g. bAsset: [ A, B, C, D]
// e.g. vaults: [ 80, 60, 60, 0]
// e.g. ratio: [1e12, 1e8, 1e20, 1e18]
// expectedM: 4e19 3e19 3e19 0
// expectedB: 4e15 3e19 3e7 0
uint256 len = _allBassets.length;
uint256[] memory redeemQuantities = new uint256[](len);
uint256[] memory ratioedBassetVaults = new uint256[](len);
uint256 totalBassetVault = 0;
// 1. Add up total vault & ratioedBassets, fail if blacklisted
for(uint256 i = 0; i < len; i++) {
if(_allBassets[i].status == BassetStatus.Blacklisted) {
return (false, "Basket contains blacklisted bAsset", redeemQuantities);
} else if(_allBassets[i].status == BassetStatus.Liquidating) {
return (false, "Basket contains liquidating bAsset", redeemQuantities);
}
// e.g. (80e14 * 1e12) / 1e8 = 80e18
// e.g. (60e18 * 1e8) / 1e8 = 60e18
uint256 ratioedBasset = _allBassets[i].vaultBalance.mulRatioTruncate(_allBassets[i].ratio);
ratioedBassetVaults[i] = ratioedBasset;
totalBassetVault = totalBassetVault.add(ratioedBasset);
}
if(totalBassetVault == 0) return (false, "Nothing in the basket to redeem", redeemQuantities);
if(_mAssetQuantity > totalBassetVault) return (false, "Not enough liquidity", redeemQuantities);
// 2. Calculate proportional weighting & non-ratioed amount
for(uint256 i = 0; i < len; i++) {
// proportional weighting
// e.g. (8e19 * 1e18) / 2e20 = 8e37 / 2e20 = 4e17 (40%)
uint256 percentageOfVault = ratioedBassetVaults[i].divPrecisely(totalBassetVault);
// e.g. (1e20 * 4e17) / 1e18 = 4e37 / 1e18 = 4e19 (40)
uint256 ratioedProportionalBasset = _mAssetQuantity.mulTruncate(percentageOfVault);
// convert back to bAsset amount
// e.g. (4e19 * 1e8) / 1e12 = 4e27 / 1e12 = 4e15
redeemQuantities[i] = ratioedProportionalBasset.divRatioPrecisely(_allBassets[i].ratio);
}
// 3. Return
return (true, "", redeemQuantities);
}
/***************************************
HELPERS
****************************************/
struct BasketStateResponse {
bool isValid;
string reason;
bool atLeastOneBroken;
uint256 overWeightCount;
bool[] isOverWeight;
uint256[] ratioedBassetVaults;
}
/**
* @dev Gets the currently overweight bAssets, and capitalises on the for loop to
* produce some other useful data. Loops through, validating the bAsset, and determining
* if it is overweight, returning the ratioed bAsset.
* @param _total Sum of collateral units in the basket
* @param _bAssets Array of all bAsset information
* @return response Struct containing calculated data
*/
function _getBasketState(uint256 _total, Basset[] memory _bAssets)
private
pure
returns (BasketStateResponse memory response)
{
uint256 len = _bAssets.length;
response = BasketStateResponse({
isValid: true,
reason: "",
atLeastOneBroken: false,
overWeightCount: 0,
isOverWeight: new bool[](len),
ratioedBassetVaults: new uint256[](len)
});
for(uint256 i = 0; i < len; i++) {
BassetStatus status = _bAssets[i].status;
if(status == BassetStatus.Blacklisted) {
response.isValid = false;
response.reason = "Basket contains blacklisted bAsset";
return response;
} else if(
status == BassetStatus.Liquidating ||
status == BassetStatus.BrokenBelowPeg ||
status == BassetStatus.BrokenAbovePeg
) {
response.atLeastOneBroken = true;
}
uint256 ratioedBasset = _bAssets[i].vaultBalance.mulRatioTruncate(_bAssets[i].ratio);
response.ratioedBassetVaults[i] = ratioedBasset;
uint256 maxWeightInUnits = _total.mulTruncate(_bAssets[i].maxWeight);
bool bAssetOverWeight = ratioedBasset > maxWeightInUnits;
if(bAssetOverWeight){
response.isOverWeight[i] = true;
response.overWeightCount += 1;
}
}
}
/**
* @dev After the redeemed bAssets have been removed from the basket, determine
* if there are any resulting overweight, or underweight
* @param _newTotal Sum of collateral units in the basket
* @param _bAssets Array of all bAsset information
* @param _ratioedBassetVaultsAfter Array of all new bAsset vaults
* @param _previouslyOverWeight Array of bools - was this bAsset already overweight
* @return underWeight Array of bools - is this bAsset now under min weight
*/
function _getOverweightBassetsAfter(
uint256 _newTotal,
Basset[] memory _bAssets,
uint256[] memory _ratioedBassetVaultsAfter,
bool[] memory _previouslyOverWeight
)
private
pure
returns (bool atLeastOneBecameOverweight)
{
uint256 len = _ratioedBassetVaultsAfter.length;
for(uint256 i = 0; i < len; i++) {
uint256 maxWeightInUnits = _newTotal.mulTruncate(_bAssets[i].maxWeight);
bool isOverweight = _ratioedBassetVaultsAfter[i] > maxWeightInUnits;
// If it was not previously overweight, and now it, then it became overweight
bool becameOverweight = !_previouslyOverWeight[i] && isOverweight;
atLeastOneBecameOverweight = atLeastOneBecameOverweight || becameOverweight;
}
}
}File 7 of 7: BPool
{"BColor.sol":{"content":"// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see \u003chttp://www.gnu.org/licenses/\u003e.\n\npragma solidity 0.5.12;\n\ncontract BColor {\n function getColor()\n external view\n returns (bytes32);\n}\n\ncontract BBronze is BColor {\n function getColor()\n external view\n returns (bytes32) {\n return bytes32(\"BRONZE\");\n }\n}\n"},"BConst.sol":{"content":"// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see \u003chttp://www.gnu.org/licenses/\u003e.\n\npragma solidity 0.5.12;\n\nimport \"./BColor.sol\";\n\ncontract BConst is BBronze {\n uint public constant BONE = 10**18;\n\n uint public constant MIN_BOUND_TOKENS = 2;\n uint public constant MAX_BOUND_TOKENS = 8;\n\n uint public constant MIN_FEE = BONE / 10**6;\n uint public constant MAX_FEE = BONE / 10;\n uint public constant EXIT_FEE = 0;\n\n uint public constant MIN_WEIGHT = BONE;\n uint public constant MAX_WEIGHT = BONE * 50;\n uint public constant MAX_TOTAL_WEIGHT = BONE * 50;\n uint public constant MIN_BALANCE = BONE / 10**12;\n\n uint public constant INIT_POOL_SUPPLY = BONE * 100;\n\n uint public constant MIN_BPOW_BASE = 1 wei;\n uint public constant MAX_BPOW_BASE = (2 * BONE) - 1 wei;\n uint public constant BPOW_PRECISION = BONE / 10**10;\n\n uint public constant MAX_IN_RATIO = BONE / 2;\n uint public constant MAX_OUT_RATIO = (BONE / 3) + 1 wei;\n}\n"},"BMath.sol":{"content":"// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see \u003chttp://www.gnu.org/licenses/\u003e.\n\npragma solidity 0.5.12;\n\nimport \"./BNum.sol\";\n\ncontract BMath is BBronze, BConst, BNum {\n /**********************************************************************************************\n // calcSpotPrice //\n // sP = spotPrice //\n // bI = tokenBalanceIn ( bI / wI ) 1 //\n // bO = tokenBalanceOut sP = ----------- * ---------- //\n // wI = tokenWeightIn ( bO / wO ) ( 1 - sF ) //\n // wO = tokenWeightOut //\n // sF = swapFee //\n **********************************************************************************************/\n function calcSpotPrice(\n uint tokenBalanceIn,\n uint tokenWeightIn,\n uint tokenBalanceOut,\n uint tokenWeightOut,\n uint swapFee\n )\n public pure\n returns (uint spotPrice)\n {\n uint numer = bdiv(tokenBalanceIn, tokenWeightIn);\n uint denom = bdiv(tokenBalanceOut, tokenWeightOut);\n uint ratio = bdiv(numer, denom);\n uint scale = bdiv(BONE, bsub(BONE, swapFee));\n return (spotPrice = bmul(ratio, scale));\n }\n\n /**********************************************************************************************\n // calcOutGivenIn //\n // aO = tokenAmountOut //\n // bO = tokenBalanceOut //\n // bI = tokenBalanceIn / / bI \\ (wI / wO) \\ //\n // aI = tokenAmountIn aO = bO * | 1 - | -------------------------- | ^ | //\n // wI = tokenWeightIn \\ \\ ( bI + ( aI * ( 1 - sF )) / / //\n // wO = tokenWeightOut //\n // sF = swapFee //\n **********************************************************************************************/\n function calcOutGivenIn(\n uint tokenBalanceIn,\n uint tokenWeightIn,\n uint tokenBalanceOut,\n uint tokenWeightOut,\n uint tokenAmountIn,\n uint swapFee\n )\n public pure\n returns (uint tokenAmountOut)\n {\n uint weightRatio = bdiv(tokenWeightIn, tokenWeightOut);\n uint adjustedIn = bsub(BONE, swapFee);\n adjustedIn = bmul(tokenAmountIn, adjustedIn);\n uint y = bdiv(tokenBalanceIn, badd(tokenBalanceIn, adjustedIn));\n uint foo = bpow(y, weightRatio);\n uint bar = bsub(BONE, foo);\n tokenAmountOut = bmul(tokenBalanceOut, bar);\n return tokenAmountOut;\n }\n\n /**********************************************************************************************\n // calcInGivenOut //\n // aI = tokenAmountIn //\n // bO = tokenBalanceOut / / bO \\ (wO / wI) \\ //\n // bI = tokenBalanceIn bI * | | ------------ | ^ - 1 | //\n // aO = tokenAmountOut aI = \\ \\ ( bO - aO ) / / //\n // wI = tokenWeightIn -------------------------------------------- //\n // wO = tokenWeightOut ( 1 - sF ) //\n // sF = swapFee //\n **********************************************************************************************/\n function calcInGivenOut(\n uint tokenBalanceIn,\n uint tokenWeightIn,\n uint tokenBalanceOut,\n uint tokenWeightOut,\n uint tokenAmountOut,\n uint swapFee\n )\n public pure\n returns (uint tokenAmountIn)\n {\n uint weightRatio = bdiv(tokenWeightOut, tokenWeightIn);\n uint diff = bsub(tokenBalanceOut, tokenAmountOut);\n uint y = bdiv(tokenBalanceOut, diff);\n uint foo = bpow(y, weightRatio);\n foo = bsub(foo, BONE);\n tokenAmountIn = bsub(BONE, swapFee);\n tokenAmountIn = bdiv(bmul(tokenBalanceIn, foo), tokenAmountIn);\n return tokenAmountIn;\n }\n\n /**********************************************************************************************\n // calcPoolOutGivenSingleIn //\n // pAo = poolAmountOut / \\ //\n // tAi = tokenAmountIn /// / // wI \\ \\\\ \\ wI \\ //\n // wI = tokenWeightIn //| tAi *| 1 - || 1 - -- | * sF || + tBi \\ -- \\ //\n // tW = totalWeight pAo=|| \\ \\ \\\\ tW / // | ^ tW | * pS - pS //\n // tBi = tokenBalanceIn \\\\ ------------------------------------- / / //\n // pS = poolSupply \\\\ tBi / / //\n // sF = swapFee \\ / //\n **********************************************************************************************/\n function calcPoolOutGivenSingleIn(\n uint tokenBalanceIn,\n uint tokenWeightIn,\n uint poolSupply,\n uint totalWeight,\n uint tokenAmountIn,\n uint swapFee\n )\n public pure\n returns (uint poolAmountOut)\n {\n // Charge the trading fee for the proportion of tokenAi\n /// which is implicitly traded to the other pool tokens.\n // That proportion is (1- weightTokenIn)\n // tokenAiAfterFee = tAi * (1 - (1-weightTi) * poolFee);\n uint normalizedWeight = bdiv(tokenWeightIn, totalWeight);\n uint zaz = bmul(bsub(BONE, normalizedWeight), swapFee); \n uint tokenAmountInAfterFee = bmul(tokenAmountIn, bsub(BONE, zaz));\n\n uint newTokenBalanceIn = badd(tokenBalanceIn, tokenAmountInAfterFee);\n uint tokenInRatio = bdiv(newTokenBalanceIn, tokenBalanceIn);\n\n // uint newPoolSupply = (ratioTi ^ weightTi) * poolSupply;\n uint poolRatio = bpow(tokenInRatio, normalizedWeight);\n uint newPoolSupply = bmul(poolRatio, poolSupply);\n poolAmountOut = bsub(newPoolSupply, poolSupply);\n return poolAmountOut;\n }\n\n /**********************************************************************************************\n // calcSingleInGivenPoolOut //\n // tAi = tokenAmountIn //(pS + pAo)\\ / 1 \\\\ //\n // pS = poolSupply || --------- | ^ | --------- || * bI - bI //\n // pAo = poolAmountOut \\\\ pS / \\(wI / tW)// //\n // bI = balanceIn tAi = -------------------------------------------- //\n // wI = weightIn / wI \\ //\n // tW = totalWeight | 1 - ---- | * sF //\n // sF = swapFee \\ tW / //\n **********************************************************************************************/\n function calcSingleInGivenPoolOut(\n uint tokenBalanceIn,\n uint tokenWeightIn,\n uint poolSupply,\n uint totalWeight,\n uint poolAmountOut,\n uint swapFee\n )\n public pure\n returns (uint tokenAmountIn)\n {\n uint normalizedWeight = bdiv(tokenWeightIn, totalWeight);\n uint newPoolSupply = badd(poolSupply, poolAmountOut);\n uint poolRatio = bdiv(newPoolSupply, poolSupply);\n \n //uint newBalTi = poolRatio^(1/weightTi) * balTi;\n uint boo = bdiv(BONE, normalizedWeight); \n uint tokenInRatio = bpow(poolRatio, boo);\n uint newTokenBalanceIn = bmul(tokenInRatio, tokenBalanceIn);\n uint tokenAmountInAfterFee = bsub(newTokenBalanceIn, tokenBalanceIn);\n // Do reverse order of fees charged in joinswap_ExternAmountIn, this way \n // ``` pAo == joinswap_ExternAmountIn(Ti, joinswap_PoolAmountOut(pAo, Ti)) ```\n //uint tAi = tAiAfterFee / (1 - (1-weightTi) * swapFee) ;\n uint zar = bmul(bsub(BONE, normalizedWeight), swapFee);\n tokenAmountIn = bdiv(tokenAmountInAfterFee, bsub(BONE, zar));\n return tokenAmountIn;\n }\n\n /**********************************************************************************************\n // calcSingleOutGivenPoolIn //\n // tAo = tokenAmountOut / / \\\\ //\n // bO = tokenBalanceOut / // pS - (pAi * (1 - eF)) \\ / 1 \\ \\\\ //\n // pAi = poolAmountIn | bO - || ----------------------- | ^ | --------- | * b0 || //\n // ps = poolSupply \\ \\\\ pS / \\(wO / tW)/ // //\n // wI = tokenWeightIn tAo = \\ \\ // //\n // tW = totalWeight / / wO \\ \\ //\n // sF = swapFee * | 1 - | 1 - ---- | * sF | //\n // eF = exitFee \\ \\ tW / / //\n **********************************************************************************************/\n function calcSingleOutGivenPoolIn(\n uint tokenBalanceOut,\n uint tokenWeightOut,\n uint poolSupply,\n uint totalWeight,\n uint poolAmountIn,\n uint swapFee\n )\n public pure\n returns (uint tokenAmountOut)\n {\n uint normalizedWeight = bdiv(tokenWeightOut, totalWeight);\n // charge exit fee on the pool token side\n // pAiAfterExitFee = pAi*(1-exitFee)\n uint poolAmountInAfterExitFee = bmul(poolAmountIn, bsub(BONE, EXIT_FEE));\n uint newPoolSupply = bsub(poolSupply, poolAmountInAfterExitFee);\n uint poolRatio = bdiv(newPoolSupply, poolSupply);\n \n // newBalTo = poolRatio^(1/weightTo) * balTo;\n uint tokenOutRatio = bpow(poolRatio, bdiv(BONE, normalizedWeight));\n uint newTokenBalanceOut = bmul(tokenOutRatio, tokenBalanceOut);\n\n uint tokenAmountOutBeforeSwapFee = bsub(tokenBalanceOut, newTokenBalanceOut);\n\n // charge swap fee on the output token side \n //uint tAo = tAoBeforeSwapFee * (1 - (1-weightTo) * swapFee)\n uint zaz = bmul(bsub(BONE, normalizedWeight), swapFee); \n tokenAmountOut = bmul(tokenAmountOutBeforeSwapFee, bsub(BONE, zaz));\n return tokenAmountOut;\n }\n\n /**********************************************************************************************\n // calcPoolInGivenSingleOut //\n // pAi = poolAmountIn // / tAo \\\\ / wO \\ \\ //\n // bO = tokenBalanceOut // | bO - -------------------------- |\\ | ---- | \\ //\n // tAo = tokenAmountOut pS - || \\ 1 - ((1 - (tO / tW)) * sF)/ | ^ \\ tW / * pS | //\n // ps = poolSupply \\\\ -----------------------------------/ / //\n // wO = tokenWeightOut pAi = \\\\ bO / / //\n // tW = totalWeight ------------------------------------------------------------- //\n // sF = swapFee ( 1 - eF ) //\n // eF = exitFee //\n **********************************************************************************************/\n function calcPoolInGivenSingleOut(\n uint tokenBalanceOut,\n uint tokenWeightOut,\n uint poolSupply,\n uint totalWeight,\n uint tokenAmountOut,\n uint swapFee\n )\n public pure\n returns (uint poolAmountIn)\n {\n\n // charge swap fee on the output token side \n uint normalizedWeight = bdiv(tokenWeightOut, totalWeight);\n //uint tAoBeforeSwapFee = tAo / (1 - (1-weightTo) * swapFee) ;\n uint zoo = bsub(BONE, normalizedWeight);\n uint zar = bmul(zoo, swapFee); \n uint tokenAmountOutBeforeSwapFee = bdiv(tokenAmountOut, bsub(BONE, zar));\n\n uint newTokenBalanceOut = bsub(tokenBalanceOut, tokenAmountOutBeforeSwapFee);\n uint tokenOutRatio = bdiv(newTokenBalanceOut, tokenBalanceOut);\n\n //uint newPoolSupply = (ratioTo ^ weightTo) * poolSupply;\n uint poolRatio = bpow(tokenOutRatio, normalizedWeight);\n uint newPoolSupply = bmul(poolRatio, poolSupply);\n uint poolAmountInAfterExitFee = bsub(poolSupply, newPoolSupply);\n\n // charge exit fee on the pool token side\n // pAi = pAiAfterExitFee/(1-exitFee)\n poolAmountIn = bdiv(poolAmountInAfterExitFee, bsub(BONE, EXIT_FEE));\n return poolAmountIn;\n }\n\n\n}\n"},"BNum.sol":{"content":"// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see \u003chttp://www.gnu.org/licenses/\u003e.\n\npragma solidity 0.5.12;\n\nimport \"./BConst.sol\";\n\ncontract BNum is BConst {\n\n function btoi(uint a)\n internal pure \n returns (uint)\n {\n return a / BONE;\n }\n\n function bfloor(uint a)\n internal pure\n returns (uint)\n {\n return btoi(a) * BONE;\n }\n\n function badd(uint a, uint b)\n internal pure\n returns (uint)\n {\n uint c = a + b;\n require(c \u003e= a, \"ERR_ADD_OVERFLOW\");\n return c;\n }\n\n function bsub(uint a, uint b)\n internal pure\n returns (uint)\n {\n (uint c, bool flag) = bsubSign(a, b);\n require(!flag, \"ERR_SUB_UNDERFLOW\");\n return c;\n }\n\n function bsubSign(uint a, uint b)\n internal pure\n returns (uint, bool)\n {\n if (a \u003e= b) {\n return (a - b, false);\n } else {\n return (b - a, true);\n }\n }\n\n function bmul(uint a, uint b)\n internal pure\n returns (uint)\n {\n uint c0 = a * b;\n require(a == 0 || c0 / a == b, \"ERR_MUL_OVERFLOW\");\n uint c1 = c0 + (BONE / 2);\n require(c1 \u003e= c0, \"ERR_MUL_OVERFLOW\");\n uint c2 = c1 / BONE;\n return c2;\n }\n\n function bdiv(uint a, uint b)\n internal pure\n returns (uint)\n {\n require(b != 0, \"ERR_DIV_ZERO\");\n uint c0 = a * BONE;\n require(a == 0 || c0 / a == BONE, \"ERR_DIV_INTERNAL\"); // bmul overflow\n uint c1 = c0 + (b / 2);\n require(c1 \u003e= c0, \"ERR_DIV_INTERNAL\"); // badd require\n uint c2 = c1 / b;\n return c2;\n }\n\n // DSMath.wpow\n function bpowi(uint a, uint n)\n internal pure\n returns (uint)\n {\n uint z = n % 2 != 0 ? a : BONE;\n\n for (n /= 2; n != 0; n /= 2) {\n a = bmul(a, a);\n\n if (n % 2 != 0) {\n z = bmul(z, a);\n }\n }\n return z;\n }\n\n // Compute b^(e.w) by splitting it into (b^e)*(b^0.w).\n // Use `bpowi` for `b^e` and `bpowK` for k iterations\n // of approximation of b^0.w\n function bpow(uint base, uint exp)\n internal pure\n returns (uint)\n {\n require(base \u003e= MIN_BPOW_BASE, \"ERR_BPOW_BASE_TOO_LOW\");\n require(base \u003c= MAX_BPOW_BASE, \"ERR_BPOW_BASE_TOO_HIGH\");\n\n uint whole = bfloor(exp); \n uint remain = bsub(exp, whole);\n\n uint wholePow = bpowi(base, btoi(whole));\n\n if (remain == 0) {\n return wholePow;\n }\n\n uint partialResult = bpowApprox(base, remain, BPOW_PRECISION);\n return bmul(wholePow, partialResult);\n }\n\n function bpowApprox(uint base, uint exp, uint precision)\n internal pure\n returns (uint)\n {\n // term 0:\n uint a = exp;\n (uint x, bool xneg) = bsubSign(base, BONE);\n uint term = BONE;\n uint sum = term;\n bool negative = false;\n\n\n // term(k) = numer / denom \n // = (product(a - i - 1, i=1--\u003ek) * x^k) / (k!)\n // each iteration, multiply previous term by (a-(k-1)) * x / k\n // continue until term is less than precision\n for (uint i = 1; term \u003e= precision; i++) {\n uint bigK = i * BONE;\n (uint c, bool cneg) = bsubSign(a, bsub(bigK, BONE));\n term = bmul(term, bmul(c, x));\n term = bdiv(term, bigK);\n if (term == 0) break;\n\n if (xneg) negative = !negative;\n if (cneg) negative = !negative;\n if (negative) {\n sum = bsub(sum, term);\n } else {\n sum = badd(sum, term);\n }\n }\n\n return sum;\n }\n\n}\n"},"BPool.sol":{"content":"// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see \u003chttp://www.gnu.org/licenses/\u003e.\n\npragma solidity 0.5.12;\n\nimport \"./BToken.sol\";\nimport \"./BMath.sol\";\n\ncontract BPool is BBronze, BToken, BMath {\n\n struct Record {\n bool bound; // is token bound to pool\n uint index; // private\n uint denorm; // denormalized weight\n uint balance;\n }\n\n event LOG_SWAP(\n address indexed caller,\n address indexed tokenIn,\n address indexed tokenOut,\n uint256 tokenAmountIn,\n uint256 tokenAmountOut\n );\n\n event LOG_JOIN(\n address indexed caller,\n address indexed tokenIn,\n uint256 tokenAmountIn\n );\n\n event LOG_EXIT(\n address indexed caller,\n address indexed tokenOut,\n uint256 tokenAmountOut\n );\n\n event LOG_CALL(\n bytes4 indexed sig,\n address indexed caller,\n bytes data\n ) anonymous;\n\n modifier _logs_() {\n emit LOG_CALL(msg.sig, msg.sender, msg.data);\n _;\n }\n\n modifier _lock_() {\n require(!_mutex, \"ERR_REENTRY\");\n _mutex = true;\n _;\n _mutex = false;\n }\n\n modifier _viewlock_() {\n require(!_mutex, \"ERR_REENTRY\");\n _;\n }\n\n bool private _mutex;\n\n address private _factory; // BFactory address to push token exitFee to\n address private _controller; // has CONTROL role\n bool private _publicSwap; // true if PUBLIC can call SWAP functions\n\n // `setSwapFee` and `finalize` require CONTROL\n // `finalize` sets `PUBLIC can SWAP`, `PUBLIC can JOIN`\n uint private _swapFee;\n bool private _finalized;\n\n address[] private _tokens;\n mapping(address=\u003eRecord) private _records;\n uint private _totalWeight;\n\n constructor() public {\n _controller = msg.sender;\n _factory = msg.sender;\n _swapFee = MIN_FEE;\n _publicSwap = false;\n _finalized = false;\n }\n\n function isPublicSwap()\n external view\n returns (bool)\n {\n return _publicSwap;\n }\n\n function isFinalized()\n external view\n returns (bool)\n {\n return _finalized;\n }\n\n function isBound(address t)\n external view\n returns (bool)\n {\n return _records[t].bound;\n }\n\n function getNumTokens()\n external view\n returns (uint) \n {\n return _tokens.length;\n }\n\n function getCurrentTokens()\n external view _viewlock_\n returns (address[] memory tokens)\n {\n return _tokens;\n }\n\n function getFinalTokens()\n external view\n _viewlock_\n returns (address[] memory tokens)\n {\n require(_finalized, \"ERR_NOT_FINALIZED\");\n return _tokens;\n }\n\n function getDenormalizedWeight(address token)\n external view\n _viewlock_\n returns (uint)\n {\n\n require(_records[token].bound, \"ERR_NOT_BOUND\");\n return _records[token].denorm;\n }\n\n function getTotalDenormalizedWeight()\n external view\n _viewlock_\n returns (uint)\n {\n return _totalWeight;\n }\n\n function getNormalizedWeight(address token)\n external view\n _viewlock_\n returns (uint)\n {\n\n require(_records[token].bound, \"ERR_NOT_BOUND\");\n uint denorm = _records[token].denorm;\n return bdiv(denorm, _totalWeight);\n }\n\n function getBalance(address token)\n external view\n _viewlock_\n returns (uint)\n {\n\n require(_records[token].bound, \"ERR_NOT_BOUND\");\n return _records[token].balance;\n }\n\n function getSwapFee()\n external view\n _viewlock_\n returns (uint)\n {\n return _swapFee;\n }\n\n function getController()\n external view\n _viewlock_\n returns (address)\n {\n return _controller;\n }\n\n function setSwapFee(uint swapFee)\n external\n _logs_\n _lock_\n { \n require(!_finalized, \"ERR_IS_FINALIZED\");\n require(msg.sender == _controller, \"ERR_NOT_CONTROLLER\");\n require(swapFee \u003e= MIN_FEE, \"ERR_MIN_FEE\");\n require(swapFee \u003c= MAX_FEE, \"ERR_MAX_FEE\");\n _swapFee = swapFee;\n }\n\n function setController(address manager)\n external\n _logs_\n _lock_\n {\n require(msg.sender == _controller, \"ERR_NOT_CONTROLLER\");\n _controller = manager;\n }\n\n function setPublicSwap(bool public_)\n external\n _logs_\n _lock_\n {\n require(!_finalized, \"ERR_IS_FINALIZED\");\n require(msg.sender == _controller, \"ERR_NOT_CONTROLLER\");\n _publicSwap = public_;\n }\n\n function finalize()\n external\n _logs_\n _lock_\n {\n require(msg.sender == _controller, \"ERR_NOT_CONTROLLER\");\n require(!_finalized, \"ERR_IS_FINALIZED\");\n require(_tokens.length \u003e= MIN_BOUND_TOKENS, \"ERR_MIN_TOKENS\");\n\n _finalized = true;\n _publicSwap = true;\n\n _mintPoolShare(INIT_POOL_SUPPLY);\n _pushPoolShare(msg.sender, INIT_POOL_SUPPLY);\n }\n\n\n function bind(address token, uint balance, uint denorm)\n external\n _logs_\n // _lock_ Bind does not lock because it jumps to `rebind`, which does\n {\n require(msg.sender == _controller, \"ERR_NOT_CONTROLLER\");\n require(!_records[token].bound, \"ERR_IS_BOUND\");\n require(!_finalized, \"ERR_IS_FINALIZED\");\n\n require(_tokens.length \u003c MAX_BOUND_TOKENS, \"ERR_MAX_TOKENS\");\n\n _records[token] = Record({\n bound: true,\n index: _tokens.length,\n denorm: 0, // balance and denorm will be validated\n balance: 0 // and set by `rebind`\n });\n _tokens.push(token);\n rebind(token, balance, denorm);\n }\n\n function rebind(address token, uint balance, uint denorm)\n public\n _logs_\n _lock_\n {\n\n require(msg.sender == _controller, \"ERR_NOT_CONTROLLER\");\n require(_records[token].bound, \"ERR_NOT_BOUND\");\n require(!_finalized, \"ERR_IS_FINALIZED\");\n\n require(denorm \u003e= MIN_WEIGHT, \"ERR_MIN_WEIGHT\");\n require(denorm \u003c= MAX_WEIGHT, \"ERR_MAX_WEIGHT\");\n require(balance \u003e= MIN_BALANCE, \"ERR_MIN_BALANCE\");\n\n // Adjust the denorm and totalWeight\n uint oldWeight = _records[token].denorm;\n if (denorm \u003e oldWeight) {\n _totalWeight = badd(_totalWeight, bsub(denorm, oldWeight));\n require(_totalWeight \u003c= MAX_TOTAL_WEIGHT, \"ERR_MAX_TOTAL_WEIGHT\");\n } else if (denorm \u003c oldWeight) {\n _totalWeight = bsub(_totalWeight, bsub(oldWeight, denorm));\n } \n _records[token].denorm = denorm;\n\n // Adjust the balance record and actual token balance\n uint oldBalance = _records[token].balance;\n _records[token].balance = balance;\n if (balance \u003e oldBalance) {\n _pullUnderlying(token, msg.sender, bsub(balance, oldBalance));\n } else if (balance \u003c oldBalance) {\n // In this case liquidity is being withdrawn, so charge EXIT_FEE\n uint tokenBalanceWithdrawn = bsub(oldBalance, balance);\n uint tokenExitFee = bmul(tokenBalanceWithdrawn, EXIT_FEE);\n _pushUnderlying(token, msg.sender, bsub(tokenBalanceWithdrawn, tokenExitFee));\n _pushUnderlying(token, _factory, tokenExitFee);\n }\n }\n\n function unbind(address token)\n external\n _logs_\n _lock_\n {\n\n require(msg.sender == _controller, \"ERR_NOT_CONTROLLER\");\n require(_records[token].bound, \"ERR_NOT_BOUND\");\n require(!_finalized, \"ERR_IS_FINALIZED\");\n\n uint tokenBalance = _records[token].balance;\n uint tokenExitFee = bmul(tokenBalance, EXIT_FEE);\n\n _totalWeight = bsub(_totalWeight, _records[token].denorm);\n\n // Swap the token-to-unbind with the last token,\n // then delete the last token\n uint index = _records[token].index;\n uint last = _tokens.length - 1;\n _tokens[index] = _tokens[last];\n _records[_tokens[index]].index = index;\n _tokens.pop();\n _records[token] = Record({\n bound: false,\n index: 0,\n denorm: 0,\n balance: 0\n });\n\n _pushUnderlying(token, msg.sender, bsub(tokenBalance, tokenExitFee));\n _pushUnderlying(token, _factory, tokenExitFee);\n }\n\n // Absorb any tokens that have been sent to this contract into the pool\n function gulp(address token)\n external\n _logs_\n _lock_\n {\n require(_records[token].bound, \"ERR_NOT_BOUND\");\n _records[token].balance = IERC20(token).balanceOf(address(this));\n }\n\n function getSpotPrice(address tokenIn, address tokenOut)\n external view\n _viewlock_\n returns (uint spotPrice)\n {\n require(_records[tokenIn].bound, \"ERR_NOT_BOUND\");\n require(_records[tokenOut].bound, \"ERR_NOT_BOUND\");\n Record storage inRecord = _records[tokenIn];\n Record storage outRecord = _records[tokenOut];\n return calcSpotPrice(inRecord.balance, inRecord.denorm, outRecord.balance, outRecord.denorm, _swapFee);\n }\n\n function getSpotPriceSansFee(address tokenIn, address tokenOut)\n external view\n _viewlock_\n returns (uint spotPrice)\n {\n require(_records[tokenIn].bound, \"ERR_NOT_BOUND\");\n require(_records[tokenOut].bound, \"ERR_NOT_BOUND\");\n Record storage inRecord = _records[tokenIn];\n Record storage outRecord = _records[tokenOut];\n return calcSpotPrice(inRecord.balance, inRecord.denorm, outRecord.balance, outRecord.denorm, 0);\n }\n\n function joinPool(uint poolAmountOut, uint[] calldata maxAmountsIn)\n external\n _logs_\n _lock_\n {\n require(_finalized, \"ERR_NOT_FINALIZED\");\n\n uint poolTotal = totalSupply();\n uint ratio = bdiv(poolAmountOut, poolTotal);\n require(ratio != 0, \"ERR_MATH_APPROX\");\n\n for (uint i = 0; i \u003c _tokens.length; i++) {\n address t = _tokens[i];\n uint bal = _records[t].balance;\n uint tokenAmountIn = bmul(ratio, bal);\n require(tokenAmountIn != 0, \"ERR_MATH_APPROX\");\n require(tokenAmountIn \u003c= maxAmountsIn[i], \"ERR_LIMIT_IN\");\n _records[t].balance = badd(_records[t].balance, tokenAmountIn);\n emit LOG_JOIN(msg.sender, t, tokenAmountIn);\n _pullUnderlying(t, msg.sender, tokenAmountIn);\n }\n _mintPoolShare(poolAmountOut);\n _pushPoolShare(msg.sender, poolAmountOut);\n }\n\n function exitPool(uint poolAmountIn, uint[] calldata minAmountsOut)\n external\n _logs_\n _lock_\n {\n require(_finalized, \"ERR_NOT_FINALIZED\");\n\n uint poolTotal = totalSupply();\n uint exitFee = bmul(poolAmountIn, EXIT_FEE);\n uint pAiAfterExitFee = bsub(poolAmountIn, exitFee);\n uint ratio = bdiv(pAiAfterExitFee, poolTotal);\n require(ratio != 0, \"ERR_MATH_APPROX\");\n\n _pullPoolShare(msg.sender, poolAmountIn);\n _pushPoolShare(_factory, exitFee);\n _burnPoolShare(pAiAfterExitFee);\n\n for (uint i = 0; i \u003c _tokens.length; i++) {\n address t = _tokens[i];\n uint bal = _records[t].balance;\n uint tokenAmountOut = bmul(ratio, bal);\n require(tokenAmountOut != 0, \"ERR_MATH_APPROX\");\n require(tokenAmountOut \u003e= minAmountsOut[i], \"ERR_LIMIT_OUT\");\n _records[t].balance = bsub(_records[t].balance, tokenAmountOut);\n emit LOG_EXIT(msg.sender, t, tokenAmountOut);\n _pushUnderlying(t, msg.sender, tokenAmountOut);\n }\n\n }\n\n\n function swapExactAmountIn(\n address tokenIn,\n uint tokenAmountIn,\n address tokenOut,\n uint minAmountOut,\n uint maxPrice\n )\n external\n _logs_\n _lock_\n returns (uint tokenAmountOut, uint spotPriceAfter)\n {\n\n require(_records[tokenIn].bound, \"ERR_NOT_BOUND\");\n require(_records[tokenOut].bound, \"ERR_NOT_BOUND\");\n require(_publicSwap, \"ERR_SWAP_NOT_PUBLIC\");\n\n Record storage inRecord = _records[address(tokenIn)];\n Record storage outRecord = _records[address(tokenOut)];\n\n require(tokenAmountIn \u003c= bmul(inRecord.balance, MAX_IN_RATIO), \"ERR_MAX_IN_RATIO\");\n\n uint spotPriceBefore = calcSpotPrice(\n inRecord.balance,\n inRecord.denorm,\n outRecord.balance,\n outRecord.denorm,\n _swapFee\n );\n require(spotPriceBefore \u003c= maxPrice, \"ERR_BAD_LIMIT_PRICE\");\n\n tokenAmountOut = calcOutGivenIn(\n inRecord.balance,\n inRecord.denorm,\n outRecord.balance,\n outRecord.denorm,\n tokenAmountIn,\n _swapFee\n );\n require(tokenAmountOut \u003e= minAmountOut, \"ERR_LIMIT_OUT\");\n\n inRecord.balance = badd(inRecord.balance, tokenAmountIn);\n outRecord.balance = bsub(outRecord.balance, tokenAmountOut);\n\n spotPriceAfter = calcSpotPrice(\n inRecord.balance,\n inRecord.denorm,\n outRecord.balance,\n outRecord.denorm,\n _swapFee\n );\n require(spotPriceAfter \u003e= spotPriceBefore, \"ERR_MATH_APPROX\"); \n require(spotPriceAfter \u003c= maxPrice, \"ERR_LIMIT_PRICE\");\n require(spotPriceBefore \u003c= bdiv(tokenAmountIn, tokenAmountOut), \"ERR_MATH_APPROX\");\n\n emit LOG_SWAP(msg.sender, tokenIn, tokenOut, tokenAmountIn, tokenAmountOut);\n\n _pullUnderlying(tokenIn, msg.sender, tokenAmountIn);\n _pushUnderlying(tokenOut, msg.sender, tokenAmountOut);\n\n return (tokenAmountOut, spotPriceAfter);\n }\n\n function swapExactAmountOut(\n address tokenIn,\n uint maxAmountIn,\n address tokenOut,\n uint tokenAmountOut,\n uint maxPrice\n )\n external\n _logs_\n _lock_ \n returns (uint tokenAmountIn, uint spotPriceAfter)\n {\n require(_records[tokenIn].bound, \"ERR_NOT_BOUND\");\n require(_records[tokenOut].bound, \"ERR_NOT_BOUND\");\n require(_publicSwap, \"ERR_SWAP_NOT_PUBLIC\");\n\n Record storage inRecord = _records[address(tokenIn)];\n Record storage outRecord = _records[address(tokenOut)];\n\n require(tokenAmountOut \u003c= bmul(outRecord.balance, MAX_OUT_RATIO), \"ERR_MAX_OUT_RATIO\");\n\n uint spotPriceBefore = calcSpotPrice(\n inRecord.balance,\n inRecord.denorm,\n outRecord.balance,\n outRecord.denorm,\n _swapFee\n );\n require(spotPriceBefore \u003c= maxPrice, \"ERR_BAD_LIMIT_PRICE\");\n\n tokenAmountIn = calcInGivenOut(\n inRecord.balance,\n inRecord.denorm,\n outRecord.balance,\n outRecord.denorm,\n tokenAmountOut,\n _swapFee\n );\n require(tokenAmountIn \u003c= maxAmountIn, \"ERR_LIMIT_IN\");\n\n inRecord.balance = badd(inRecord.balance, tokenAmountIn);\n outRecord.balance = bsub(outRecord.balance, tokenAmountOut);\n\n spotPriceAfter = calcSpotPrice(\n inRecord.balance,\n inRecord.denorm,\n outRecord.balance,\n outRecord.denorm,\n _swapFee\n );\n require(spotPriceAfter \u003e= spotPriceBefore, \"ERR_MATH_APPROX\");\n require(spotPriceAfter \u003c= maxPrice, \"ERR_LIMIT_PRICE\");\n require(spotPriceBefore \u003c= bdiv(tokenAmountIn, tokenAmountOut), \"ERR_MATH_APPROX\");\n\n emit LOG_SWAP(msg.sender, tokenIn, tokenOut, tokenAmountIn, tokenAmountOut);\n\n _pullUnderlying(tokenIn, msg.sender, tokenAmountIn);\n _pushUnderlying(tokenOut, msg.sender, tokenAmountOut);\n\n return (tokenAmountIn, spotPriceAfter);\n }\n\n\n function joinswapExternAmountIn(address tokenIn, uint tokenAmountIn, uint minPoolAmountOut)\n external\n _logs_\n _lock_\n returns (uint poolAmountOut)\n\n { \n require(_finalized, \"ERR_NOT_FINALIZED\");\n require(_records[tokenIn].bound, \"ERR_NOT_BOUND\");\n require(tokenAmountIn \u003c= bmul(_records[tokenIn].balance, MAX_IN_RATIO), \"ERR_MAX_IN_RATIO\");\n\n Record storage inRecord = _records[tokenIn];\n\n poolAmountOut = calcPoolOutGivenSingleIn(\n inRecord.balance,\n inRecord.denorm,\n _totalSupply,\n _totalWeight,\n tokenAmountIn,\n _swapFee\n );\n\n require(poolAmountOut \u003e= minPoolAmountOut, \"ERR_LIMIT_OUT\");\n\n inRecord.balance = badd(inRecord.balance, tokenAmountIn);\n\n emit LOG_JOIN(msg.sender, tokenIn, tokenAmountIn);\n\n _mintPoolShare(poolAmountOut);\n _pushPoolShare(msg.sender, poolAmountOut);\n _pullUnderlying(tokenIn, msg.sender, tokenAmountIn);\n\n return poolAmountOut;\n }\n\n function joinswapPoolAmountOut(address tokenIn, uint poolAmountOut, uint maxAmountIn)\n external\n _logs_\n _lock_\n returns (uint tokenAmountIn)\n {\n require(_finalized, \"ERR_NOT_FINALIZED\");\n require(_records[tokenIn].bound, \"ERR_NOT_BOUND\");\n\n Record storage inRecord = _records[tokenIn];\n\n tokenAmountIn = calcSingleInGivenPoolOut(\n inRecord.balance,\n inRecord.denorm,\n _totalSupply,\n _totalWeight,\n poolAmountOut,\n _swapFee\n );\n\n require(tokenAmountIn != 0, \"ERR_MATH_APPROX\");\n require(tokenAmountIn \u003c= maxAmountIn, \"ERR_LIMIT_IN\");\n \n require(tokenAmountIn \u003c= bmul(_records[tokenIn].balance, MAX_IN_RATIO), \"ERR_MAX_IN_RATIO\");\n\n inRecord.balance = badd(inRecord.balance, tokenAmountIn);\n\n emit LOG_JOIN(msg.sender, tokenIn, tokenAmountIn);\n\n _mintPoolShare(poolAmountOut);\n _pushPoolShare(msg.sender, poolAmountOut);\n _pullUnderlying(tokenIn, msg.sender, tokenAmountIn);\n\n return tokenAmountIn;\n }\n\n function exitswapPoolAmountIn(address tokenOut, uint poolAmountIn, uint minAmountOut)\n external\n _logs_\n _lock_\n returns (uint tokenAmountOut)\n {\n require(_finalized, \"ERR_NOT_FINALIZED\");\n require(_records[tokenOut].bound, \"ERR_NOT_BOUND\");\n\n Record storage outRecord = _records[tokenOut];\n\n tokenAmountOut = calcSingleOutGivenPoolIn(\n outRecord.balance,\n outRecord.denorm,\n _totalSupply,\n _totalWeight,\n poolAmountIn,\n _swapFee\n );\n\n require(tokenAmountOut \u003e= minAmountOut, \"ERR_LIMIT_OUT\");\n \n require(tokenAmountOut \u003c= bmul(_records[tokenOut].balance, MAX_OUT_RATIO), \"ERR_MAX_OUT_RATIO\");\n\n outRecord.balance = bsub(outRecord.balance, tokenAmountOut);\n\n uint exitFee = bmul(poolAmountIn, EXIT_FEE);\n\n emit LOG_EXIT(msg.sender, tokenOut, tokenAmountOut);\n\n _pullPoolShare(msg.sender, poolAmountIn);\n _burnPoolShare(bsub(poolAmountIn, exitFee));\n _pushPoolShare(_factory, exitFee);\n _pushUnderlying(tokenOut, msg.sender, tokenAmountOut);\n\n return tokenAmountOut;\n }\n\n function exitswapExternAmountOut(address tokenOut, uint tokenAmountOut, uint maxPoolAmountIn)\n external\n _logs_\n _lock_\n returns (uint poolAmountIn)\n {\n require(_finalized, \"ERR_NOT_FINALIZED\");\n require(_records[tokenOut].bound, \"ERR_NOT_BOUND\");\n require(tokenAmountOut \u003c= bmul(_records[tokenOut].balance, MAX_OUT_RATIO), \"ERR_MAX_OUT_RATIO\");\n\n Record storage outRecord = _records[tokenOut];\n\n poolAmountIn = calcPoolInGivenSingleOut(\n outRecord.balance,\n outRecord.denorm,\n _totalSupply,\n _totalWeight,\n tokenAmountOut,\n _swapFee\n );\n\n require(poolAmountIn != 0, \"ERR_MATH_APPROX\");\n require(poolAmountIn \u003c= maxPoolAmountIn, \"ERR_LIMIT_IN\");\n\n outRecord.balance = bsub(outRecord.balance, tokenAmountOut);\n\n uint exitFee = bmul(poolAmountIn, EXIT_FEE);\n\n emit LOG_EXIT(msg.sender, tokenOut, tokenAmountOut);\n\n _pullPoolShare(msg.sender, poolAmountIn);\n _burnPoolShare(bsub(poolAmountIn, exitFee));\n _pushPoolShare(_factory, exitFee);\n _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); \n\n return poolAmountIn;\n }\n\n\n // ==\n // \u0027Underlying\u0027 token-manipulation functions make external calls but are NOT locked\n // You must `_lock_` or otherwise ensure reentry-safety\n\n function _pullUnderlying(address erc20, address from, uint amount)\n internal\n {\n bool xfer = IERC20(erc20).transferFrom(from, address(this), amount);\n require(xfer, \"ERR_ERC20_FALSE\");\n }\n\n function _pushUnderlying(address erc20, address to, uint amount)\n internal\n {\n bool xfer = IERC20(erc20).transfer(to, amount);\n require(xfer, \"ERR_ERC20_FALSE\");\n }\n\n function _pullPoolShare(address from, uint amount)\n internal\n {\n _pull(from, amount);\n }\n\n function _pushPoolShare(address to, uint amount)\n internal\n {\n _push(to, amount);\n }\n\n function _mintPoolShare(uint amount)\n internal\n {\n _mint(amount);\n }\n\n function _burnPoolShare(uint amount)\n internal\n {\n _burn(amount);\n }\n\n}\n"},"BToken.sol":{"content":"// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see \u003chttp://www.gnu.org/licenses/\u003e.\n\npragma solidity 0.5.12;\n\nimport \"./BNum.sol\";\n\n// Highly opinionated token implementation\n\ninterface IERC20 {\n event Approval(address indexed src, address indexed dst, uint amt);\n event Transfer(address indexed src, address indexed dst, uint amt);\n\n function totalSupply() external view returns (uint);\n function balanceOf(address whom) external view returns (uint);\n function allowance(address src, address dst) external view returns (uint);\n\n function approve(address dst, uint amt) external returns (bool);\n function transfer(address dst, uint amt) external returns (bool);\n function transferFrom(\n address src, address dst, uint amt\n ) external returns (bool);\n}\n\ncontract BTokenBase is BNum {\n\n mapping(address =\u003e uint) internal _balance;\n mapping(address =\u003e mapping(address=\u003euint)) internal _allowance;\n uint internal _totalSupply;\n\n event Approval(address indexed src, address indexed dst, uint amt);\n event Transfer(address indexed src, address indexed dst, uint amt);\n\n function _mint(uint amt) internal {\n _balance[address(this)] = badd(_balance[address(this)], amt);\n _totalSupply = badd(_totalSupply, amt);\n emit Transfer(address(0), address(this), amt);\n }\n\n function _burn(uint amt) internal {\n require(_balance[address(this)] \u003e= amt, \"ERR_INSUFFICIENT_BAL\");\n _balance[address(this)] = bsub(_balance[address(this)], amt);\n _totalSupply = bsub(_totalSupply, amt);\n emit Transfer(address(this), address(0), amt);\n }\n\n function _move(address src, address dst, uint amt) internal {\n require(_balance[src] \u003e= amt, \"ERR_INSUFFICIENT_BAL\");\n _balance[src] = bsub(_balance[src], amt);\n _balance[dst] = badd(_balance[dst], amt);\n emit Transfer(src, dst, amt);\n }\n\n function _push(address to, uint amt) internal {\n _move(address(this), to, amt);\n }\n\n function _pull(address from, uint amt) internal {\n _move(from, address(this), amt);\n }\n}\n\ncontract BToken is BTokenBase, IERC20 {\n\n string private _name = \"Balancer Pool Token\";\n string private _symbol = \"BPT\";\n uint8 private _decimals = 18;\n\n function name() public view returns (string memory) {\n return _name;\n }\n\n function symbol() public view returns (string memory) {\n return _symbol;\n }\n\n function decimals() public view returns(uint8) {\n return _decimals;\n }\n\n function allowance(address src, address dst) external view returns (uint) {\n return _allowance[src][dst];\n }\n\n function balanceOf(address whom) external view returns (uint) {\n return _balance[whom];\n }\n\n function totalSupply() public view returns (uint) {\n return _totalSupply;\n }\n\n function approve(address dst, uint amt) external returns (bool) {\n _allowance[msg.sender][dst] = amt;\n emit Approval(msg.sender, dst, amt);\n return true;\n }\n\n function increaseApproval(address dst, uint amt) external returns (bool) {\n _allowance[msg.sender][dst] = badd(_allowance[msg.sender][dst], amt);\n emit Approval(msg.sender, dst, _allowance[msg.sender][dst]);\n return true;\n }\n\n function decreaseApproval(address dst, uint amt) external returns (bool) {\n uint oldValue = _allowance[msg.sender][dst];\n if (amt \u003e oldValue) {\n _allowance[msg.sender][dst] = 0;\n } else {\n _allowance[msg.sender][dst] = bsub(oldValue, amt);\n }\n emit Approval(msg.sender, dst, _allowance[msg.sender][dst]);\n return true;\n }\n\n function transfer(address dst, uint amt) external returns (bool) {\n _move(msg.sender, dst, amt);\n return true;\n }\n\n function transferFrom(address src, address dst, uint amt) external returns (bool) {\n require(msg.sender == src || amt \u003c= _allowance[src][msg.sender], \"ERR_BTOKEN_BAD_CALLER\");\n _move(src, dst, amt);\n if (msg.sender != src \u0026\u0026 _allowance[src][msg.sender] != uint256(-1)) {\n _allowance[src][msg.sender] = bsub(_allowance[src][msg.sender], amt);\n emit Approval(msg.sender, dst, _allowance[src][msg.sender]);\n }\n return true;\n }\n}\n"}}