Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ package-lock.json

hs_err_*.log
scripts

CLAUDE.md
GEMINI.md
7 changes: 7 additions & 0 deletions contracts/interfaces/ITreasury.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.10;

interface ITreasury {
function mintPosition(uint256 expiry, uint256[2] calldata anchorPrices, uint256 amount, address maker) external;
}
4 changes: 4 additions & 0 deletions contracts/mocks/MockAavePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,8 @@ contract MockAavePool {
return reserveData;
}

function getReserveNormalizedIncome(address asset) external view returns (uint256 index) {
require(asset == address(token), "Invalid asset");
index = reserveData.liquidityIndex > 0 ? reserveData.liquidityIndex : 1e27; // Default to 1e27 if liquidityIndex is zero
}
}
108 changes: 108 additions & 0 deletions contracts/treasury/AAVETreasury.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.10;

import "./TreasuryBase.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IPool} from "@aave/core-v3/contracts/interfaces/IPool.sol";
import {IAToken} from "@aave/core-v3/contracts/interfaces/IAToken.sol";

contract AAVETreasury is TreasuryBase {
using SafeERC20 for IERC20;

IPool public immutable pool;
IAToken public immutable aToken;
uint16 private constant REFERRAL_CODE = 0;

constructor(
IERC20 asset,
IPool aavePool,
IAutomatorFactory factory_
)
TreasuryBase(asset, factory_)
{
pool = aavePool;
aToken = IAToken(pool.getReserveData(address(asset)).aTokenAddress);
asset.safeApprove(address(pool), type(uint256).max);
}

function mintPosition(uint256 expiry, uint256[2] calldata anchorPrices, uint256 amount, address maker) external override nonReentrant onlyVaults {
require(factory.makers(maker), "Treasury: signer is not a maker");
uint256 index = pool.getReserveNormalizedIncome(address(asset())); // ray
uint256 scaled = (amount * 1e27) / index;
require(scaled > 0, "Treasury: scaled amount must be greater than zero");
bytes32 id = keccak256(abi.encodePacked(msg.sender, expiry, anchorPrices));
if (_positions[id].amount == 0) {
_positions[id].vault = msg.sender;
_positions[id].expiry = expiry;
_positions[id].anchorPrices = anchorPrices;
expiries[expiry].push(id);
}
_positions[id].amount += scaled;
totalPositions += scaled;
if (minExpiry == 0 || expiry < minExpiry) {
minExpiry = expiry;
}
IERC20(address(aToken)).safeTransfer(msg.sender, amount);
}

function mint(uint256, address) public pure override returns (uint256) {
revert("AAVETreasury: minting shares is not supported");
}

function withdraw(uint256, address, address) public pure override returns (uint256) {
revert("AAVETreasury: withdrawing assets is not supported, use redeem instead");
}

function maxMint(address) public pure override returns (uint256) {
return 0;
}

function maxWithdraw(address) public pure override returns (uint256) {
return 0;
}

function previewMint(uint256) public pure override returns (uint256) {
return 0;
}

function previewWithdraw(uint256) public pure override returns (uint256) {
return 0;
}

function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override {
super._deposit(caller, receiver, assets, shares);
pool.supply(
address(asset()),
assets,
address(this),
REFERRAL_CODE
);
}

function _withdraw(
address caller,
address receiver,
address owner,
uint256 assets,
uint256 shares
) internal override {
if (caller != owner) {
_spendAllowance(owner, caller, shares);
}

_burn(owner, shares);
uint256 amount = pool.withdraw(
address(asset()),
totalSupply() > 0 ? assets - assets / 100 : assets,
receiver
);

emit Withdraw(caller, receiver, owner, amount, shares);
}

function totalAssets() public view override returns (uint256) {
uint256 index = pool.getReserveNormalizedIncome(address(asset())); // ray
return aToken.balanceOf(address(this)) + totalPositions * index / 1e27;
}
}
83 changes: 83 additions & 0 deletions contracts/treasury/RCHTreasury.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.10;

import "./TreasuryBase.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";

interface IZenRCH {
function mint(uint256 amount) external returns (uint256);
function withdraw(address to, uint256 shares) external returns (uint256);
}

contract RCHTreasury is TreasuryBase {
using SafeERC20 for IERC20;
using Math for uint256;

IERC20 public immutable rch;

constructor(
IERC20 rch_,
IERC20 asset,
IAutomatorFactory factory_
)
TreasuryBase(asset, factory_)
{
rch = rch_;
rch.safeApprove(address(asset), type(uint256).max);
}

function deposit(uint256 amount, address receiver) public override nonReentrant returns (uint256 shares) {
_burnPositions();
rch.safeTransferFrom(_msgSender(), address(this), amount);
uint256 assets = IZenRCH(asset()).mint(amount);
require(assets <= maxDeposit(receiver), "ERC4626: deposit more than max");
shares = assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1 - assets, Math.Rounding.Down);
_mint(receiver, shares);

emit Deposit(_msgSender(), receiver, amount, shares);
return shares;
}

function mint(uint256, address) public pure override returns (uint256) {
revert("RCHTreasury: minting is not supported, use deposit instead");
}

function withdraw(uint256, address, address) public pure override returns (uint256) {
revert("RCHTreasury: withdrawing is not supported, use redeem instead");
}

function maxMint(address) public pure override returns (uint256) {
return 0;
}

function maxWithdraw(address) public pure override returns (uint256) {
return 0;
}

function previewMint(uint256) public pure override returns (uint256) {
return 0;
}

function previewWithdraw(uint256) public pure override returns (uint256) {
return 0;
}

function _withdraw(
address caller,
address receiver,
address owner,
uint256 assets,
uint256 shares
) internal override {
if (caller != owner) {
_spendAllowance(owner, caller, shares);
}

_burn(owner, shares);
uint256 amount = IZenRCH(asset()).withdraw(receiver, totalSupply() > 0 ? assets - assets / 100 : assets);

emit Withdraw(caller, receiver, owner, amount, shares);
}
}
13 changes: 13 additions & 0 deletions contracts/treasury/Treasury.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

import "./TreasuryBase.sol";

contract Treasury is TreasuryBase {
constructor(
IERC20 asset,
IAutomatorFactory factory_
)
TreasuryBase(asset, factory_)
{}
}
Loading