contract DaiHrd is ERC777, MakerFunctions {
	event Deposit(address indexed from, uint256 depositedAttodai, uint256 mintedAttodaiHrd);
	event Withdrawal(address indexed from, address indexed to, uint256 withdrawnAttodai, uint256 burnedAttodaiHrd);
	event DepositVatDai(address indexed account, uint256 depositedAttorontodai, uint256 mintedAttodaiHrd);
	event WithdrawalVatDai(address indexed from, address indexed to, uint256 withdrawnAttorontodai, uint256 burnedAttodaiHrd);

	// uses this super constructor syntax instead of the preferred alternative syntax because my editor doesn't like the class syntax
	constructor(ReverseRegistrar reverseRegistrar) ERC777("DAI-HRD", "DAI-HRD", new address[](0)) public {
		dai.approve(address(daiJoin), uint(-1));
		vat.hope(address(pot));
		vat.hope(address(daiJoin));
		if (reverseRegistrar != ReverseRegistrar(0)) {
			reverseRegistrar.setName("dai-hrd.eth");
		}
	}

	function deposit(uint256 attodai) external returns(uint256 attodaiHrd) {
		dai.transferFrom(msg.sender, address(this), attodai);
		daiJoin.join(address(this), dai.balanceOf(address(this)));
		uint256 depositedAttopot = depositVatDaiForAccount(msg.sender);
		emit Deposit(msg.sender, attodai, depositedAttopot);
		return depositedAttopot;
	}

	// If the user has vat dai directly (after performing vault actions, for instance), they don't need to create the DAI ERC20 just so we can burn it, we'll accept vat dai
	function depositVatDai(uint256 attorontovatDai) external returns(uint256 attodaiHrd) {
		vat.move(msg.sender, address(this), attorontovatDai);
		uint256 depositedAttopot = depositVatDaiForAccount(msg.sender);
		emit DepositVatDai(msg.sender, attorontovatDai, depositedAttopot);
		return depositedAttopot;
	}

	function withdrawTo(address recipient, uint256 attodaiHrd) external returns(uint256 attodai) {
		// Don't need rontodaiPerPot, so we don't call updateAndFetchChi
		if (pot.rho() != now) pot.drip();
		return withdraw(recipient, attodaiHrd);
	}

	function withdrawToDenominatedInDai(address recipient, uint256 attodai) external returns(uint256 attodaiHrd) {
		uint256 rontodaiPerPot = updateAndFetchChi();
		attodaiHrd = convertAttodaiToAttodaiHrd(attodai, rontodaiPerPot);
		uint256 attodaiWithdrawn = withdraw(recipient, attodaiHrd);
		require(attodaiWithdrawn >= attodai, "DaiHrd/withdrawToDenominatedInDai: Not withdrawing enough DAI to cover request");
		return attodaiHrd;
	}

	function withdrawVatDai(address recipient, uint256 attodaiHrd) external returns(uint256 attorontodai) {
		require(recipient != address(0) && recipient != address(this), "DaiHrd/withdrawVatDai: Invalid recipient");
		// Don't need rontodaiPerPot, so we don't call updateAndFetchChi
		if (pot.rho() != now) pot.drip();
		_burn(address(0), msg.sender, attodaiHrd, new bytes(0), new bytes(0));
		pot.exit(attodaiHrd);
		attorontodai = vat.dai(address(this));
		vat.move(address(this), recipient, attorontodai);
		emit WithdrawalVatDai(msg.sender, recipient, attorontodai, attodaiHrd);
		return attorontodai;
	}

	// Dai specific functions. These functions all behave similar to standard ERC777 functions with input or output denominated in Dai instead of DaiHrd
	function balanceOfDenominatedInDai(address tokenHolder) external view returns(uint256 attodai) {
		uint256 rontodaiPerPot = calculatedChi();
		uint256 attodaiHrd = balanceOf(tokenHolder);
		return convertAttodaiHrdToAttodai(attodaiHrd, rontodaiPerPot);
	}

	function totalSupplyDenominatedInDai() external view returns(uint256 attodai) {
		uint256 rontodaiPerPot = calculatedChi();
		return convertAttodaiHrdToAttodai(totalSupply(), rontodaiPerPot);
	}

	function sendDenominatedInDai(address recipient, uint256 attodai, bytes calldata data) external {
		uint256 rontodaiPerPot = calculatedChi();
		uint256 attodaiHrd = convertAttodaiToAttodaiHrd(attodai, rontodaiPerPot);
		_send(_msgSender(), _msgSender(), recipient, attodaiHrd, data, "", true);
	}

	function burnDenominatedInDai(uint256 attodai, bytes calldata data) external {
		uint256 rontodaiPerPot = calculatedChi();
		uint256 attodaiHrd = convertAttodaiToAttodaiHrd(attodai, rontodaiPerPot);
		_burn(_msgSender(), _msgSender(), attodaiHrd, data, "");
	}

	function operatorSendDenominatedInDai(address sender, address recipient, uint256 attodai, bytes calldata data, bytes calldata operatorData) external {
		require(isOperatorFor(_msgSender(), sender), "ERC777: caller is not an operator for holder");

		uint256 rontodaiPerPot = calculatedChi();
		uint256 attodaiHrd = convertAttodaiToAttodaiHrd(attodai, rontodaiPerPot);
		_send(_msgSender(), sender, recipient, attodaiHrd, data, operatorData, true);
	}

	function operatorBurnDenominatedInDai(address account, uint256 attodai, bytes calldata data, bytes calldata operatorData) external {
		require(isOperatorFor(_msgSender(), account), "ERC777: caller is not an operator for holder");

		uint256 rontodaiPerPot = calculatedChi();
		uint256 attodaiHrd = convertAttodaiToAttodaiHrd(attodai, rontodaiPerPot);
		_burn(_msgSender(), account, attodaiHrd, data, operatorData);
	}

	// Utility Functions
	function calculatedChi() public view returns (uint256 rontodaiPerPot) {
		// mirrors Maker's calculation: rmul(rpow(dsr, now - rho, ONE), chi);
		return safeMul27(rpow(pot.dsr(), now - pot.rho(), 10 ** 27), pot.chi());
	}

	function convertAttodaiToAttodaiHrd(uint256 attodai, uint256 rontodaiPerPot ) private pure returns (uint256 attodaiHrd) {
		// + 1 is to compensate rounding? since attodaiHrd is rounded down
		return attodai.mul(10 ** 27).add(rontodaiPerPot - 1).div(rontodaiPerPot);
	}

	function convertAttodaiHrdToAttodai(uint256 attodaiHrd, uint256 rontodaiPerPot ) private pure returns (uint256 attodai) {
		return attodaiHrd.mul(rontodaiPerPot).div(10 ** 27);
	}

	function updateAndFetchChi() private returns (uint256 rontodaiPerPot) {
		return (pot.rho() == now) ? pot.chi() : pot.drip();
	}

	// Takes whatever vat dai has already been transferred to DaiHrd, gives to pot (DSR) and mints tokens for user
	function depositVatDaiForAccount(address account) private returns (uint256 attopotDeposited) {
		uint256 rontodaiPerPot = updateAndFetchChi();
		uint256 attopotToDeposit = vat.dai(address(this)) / rontodaiPerPot;
		pot.join(attopotToDeposit);
		_mint(address(0), account, attopotToDeposit, new bytes(0), new bytes(0));
		return attopotToDeposit;
	}

	// Internal implementations of functions with multiple entrypoints. drip() should be called prior to this call
	function withdraw(address recipient, uint256 attodaiHrd) private returns(uint256 attodaiWithdrawn) {
		require(recipient != address(0) && recipient != address(this), "DaiHrd/withdraw: Invalid recipient");
		_burn(address(0), msg.sender, attodaiHrd, new bytes(0), new bytes(0));
		pot.exit(attodaiHrd);
		daiJoin.exit(address(this), vat.dai(address(this)) / 10**27);
		uint256 attodai = dai.balanceOf(address(this));
		dai.transfer(recipient, attodai);
		emit Withdrawal(msg.sender, recipient, attodai, attodaiHrd);
		return attodai;
	}
}
function depositVatDai(uint256 attorontovatDai) external returns(uint256 attodaiHrd) {
	vat.move(msg.sender, address(this), attorontovatDai);
	uint256 depositedAttopot = depositVatDaiForAccount(msg.sender);
	emit DepositVatDai(msg.sender, attorontovatDai, depositedAttopot);
	return depositedAttopot;
}

// If the user has vat dai directly (after performing vault actions, for instance), they don't need to create the DAI ERC20 just so we can burn it, we'll accept vat dai