Files
smom-dbis-138/contracts/flash/SimpleERC3156FlashVault.sol
defiQUG 76aa419320 feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault.
- Token-aggregation service routes, planner, chain config, relay env templates.
- Config snapshots and multi-chain deployment markdown updates.
- gitignore services/btc-intake/dist/ (tsc output); do not track dist.

Run forge build && forge test before deploy (large solc graph).

Made-with: Cursor
2026-04-07 23:40:52 -07:00

141 lines
5.5 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IERC3156FlashLender} from "@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol";
import {IERC3156FlashBorrower} from "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
/**
* @title SimpleERC3156FlashVault
* @notice Minimal ERC-3156 flash lender: whitelist per token, flat bps fee, same-block repayment invariant.
* @dev Intended for Chain 138 / internal atomic workflows when no external flash source exists.
*/
contract SimpleERC3156FlashVault is IERC3156FlashLender, Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;
bytes32 private constant _RETURN_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan");
/// @notice Maximum owner-configurable fee (10%). Prevents griefing at 100% fee.
uint256 public constant MAX_FEE_BPS = 1000;
/// @notice Fee on borrowed amount, basis points (10_000 = 100%).
uint256 public feeBps;
mapping(address token => bool) public supportedToken;
/// @notice When true, only `approvedBorrower[receiver]` may be used as the flash callback contract.
bool public borrowerAllowlistEnabled;
mapping(address borrower => bool) public approvedBorrower;
/// @notice Cumulative fees retained by the vault per token (for ops / accounting).
mapping(address token => uint256) public totalFeesCollected;
/// @notice initiator = flashLoan caller; receiver = IERC3156FlashBorrower callback target.
event FlashLoan(address indexed initiator, IERC3156FlashBorrower indexed receiver, address indexed token, uint256 amount, uint256 fee);
event FeeBpsUpdated(uint256 feeBps);
event TokenSupportUpdated(address indexed token, bool supported);
event TokensRescued(address indexed token, address indexed to, uint256 amount);
event BorrowerAllowlistEnabledUpdated(bool enabled);
event BorrowerApprovalUpdated(address indexed borrower, bool approved);
error UnsupportedToken();
error ZeroAmount();
error FeeTooHigh();
error InvalidCallback();
error RepaymentFailed();
error ZeroRescue();
error ZeroRecipient();
error BorrowerNotApproved();
constructor(address initialOwner, uint256 initialFeeBps) Ownable(initialOwner) {
_setFeeBps(initialFeeBps);
}
function setFeeBps(uint256 newFeeBps) external onlyOwner {
_setFeeBps(newFeeBps);
}
function setTokenSupported(address token, bool supported) external onlyOwner {
supportedToken[token] = supported;
emit TokenSupportUpdated(token, supported);
}
function setBorrowerAllowlistEnabled(bool enabled) external onlyOwner {
borrowerAllowlistEnabled = enabled;
emit BorrowerAllowlistEnabledUpdated(enabled);
}
function setBorrowerApproved(address borrower, bool approved) external onlyOwner {
approvedBorrower[borrower] = approved;
emit BorrowerApprovalUpdated(borrower, approved);
}
/// @notice Operator alias for `flashFee` (same revert rules).
function previewFlashFee(address token, uint256 amount) external view returns (uint256) {
return flashFee(token, amount);
}
/// @notice Owner-only recovery (mis-seeded asset, deprecated token, migration). Does not bypass flash invariants on active loans in the same tx.
function rescueTokens(address token, uint256 amount, address to) external onlyOwner {
if (amount == 0) revert ZeroRescue();
if (to == address(0)) revert ZeroRecipient();
IERC20(token).safeTransfer(to, amount);
emit TokensRescued(token, to, amount);
}
function maxFlashLoan(address token) external view override returns (uint256) {
if (!supportedToken[token]) {
return 0;
}
return IERC20(token).balanceOf(address(this));
}
function flashFee(address token, uint256 amount) public view override returns (uint256) {
if (!supportedToken[token]) {
revert UnsupportedToken();
}
return (amount * feeBps) / 10_000;
}
function flashLoan(
IERC3156FlashBorrower receiver,
address token,
uint256 amount,
bytes calldata data
) external override nonReentrant returns (bool) {
if (!supportedToken[token]) revert UnsupportedToken();
if (amount == 0) revert ZeroAmount();
if (borrowerAllowlistEnabled && !approvedBorrower[address(receiver)]) {
revert BorrowerNotApproved();
}
uint256 fee = flashFee(token, amount);
uint256 balanceBefore = IERC20(token).balanceOf(address(this));
if (balanceBefore < amount) revert RepaymentFailed();
IERC20(token).safeTransfer(address(receiver), amount);
bytes32 retval = receiver.onFlashLoan(msg.sender, token, amount, fee, data);
if (retval != _RETURN_VALUE) revert InvalidCallback();
if (IERC20(token).balanceOf(address(this)) < balanceBefore + fee) {
revert RepaymentFailed();
}
totalFeesCollected[token] += fee;
emit FlashLoan(msg.sender, receiver, token, amount, fee);
return true;
}
function _setFeeBps(uint256 newFeeBps) internal {
if (newFeeBps > MAX_FEE_BPS) revert FeeTooHigh();
feeBps = newFeeBps;
emit FeeBpsUpdated(newFeeBps);
}
}