编译器
0.8.16+commit.07a7930e
文件 1 的 10:Address.sol
pragma solidity ^0.8.1;
library Address {
function isContract(address account) internal view returns (bool) {
return account.code.length > 0;
}
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
文件 2 的 10:Context.sol
pragma solidity ^0.8.0;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
文件 3 的 10:Helper.sol
pragma solidity 0.8.16;
import {IStaking} from "../interfaces/IStaking.sol";
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {IVirtualRebaseViewer} from "../Token/VirtualRebaseViewer.sol";
interface IBalancerVault {
enum SwapKind {
GIVEN_IN,
GIVEN_OUT
}
struct FundManagement {
address sender;
bool fromInternalBalance;
address payable recipient;
bool toInternalBalance;
}
struct SingleSwap {
bytes32 poolId;
SwapKind kind;
address assetIn;
address assetOut;
uint256 amount;
bytes userData;
}
function getPool(bytes32 poolId) external view returns (address, bytes memory);
function getPoolTokens(bytes32 poolId)
external
view
returns (address[] memory tokens, uint256[] memory balances, uint256 lastChangeBlock);
function swap(SingleSwap memory singleSwap, FundManagement memory funds, uint256 limit, uint256 deadline)
external
payable
returns (uint256);
}
interface IBalancerPool {
function getNormalizedWeights() external view returns (uint256[] memory);
function getSwapFeePercentage() external view returns (uint256);
}
interface ICurveZap {
function get_dy(address, uint256, uint256, uint256) external view returns (uint256);
function exchange(address, uint256, uint256, uint256, uint256) external payable returns (uint256);
}
interface ICurvePool {
function get_dy(uint256, uint256, uint256) external view returns (uint256);
function coins(uint256) external view returns (address);
}
interface IMaverickPool {
function swap(
address recipient,
uint256 amount,
bool tokenAIn,
bool exactOutput,
uint256 sqrtPriceLimit,
bytes calldata data
) external returns (uint256 amountIn, uint256 amountOut);
struct SwapCallbackData {
bytes path;
address payer;
bool exactOutput;
}
}
interface IMaverickFactory {
function isFactoryPool(address) external view returns (bool);
}
library RouterConstants {
IERC20 internal constant usdc = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
IERC20 internal constant coil = IERC20(0x823E1B82cE1Dc147Bbdb25a203f046aFab1CE918);
IERC20 internal constant spiral = IERC20(0x85b6ACaBa696B9E4247175274F8263F99b4B9180);
IStaking internal constant staking = IStaking(0x6701E792b7CD344BaE763F27099eEb314A4b4943);
ICurveZap internal constant curveZap = ICurveZap(0x5De4EF4879F4fe3bBADF2227D2aC5d0E2D76C895);
address internal constant curvePool = 0xAF4264916B467e2c9C8aCF07Acc22b9EDdDaDF33;
IBalancerVault internal constant balVault = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
IBalancerPool internal constant balancerPool = IBalancerPool(0x42FBD9F666AaCC0026ca1B88C94259519e03dd67);
bytes32 internal constant balancerPoolId = 0x42fbd9f666aacc0026ca1b88c94259519e03dd67000200000000000000000507;
IVirtualRebaseViewer internal constant virtualRebaseViewer =
IVirtualRebaseViewer(0xab1789b0A1830d897F55e7aDAE87612264271309);
IMaverickPool internal constant maverickPool = IMaverickPool(0xF04984FDc904F310D3ca0B7B105453E14129CDa2);
IMaverickFactory internal constant maverickFactory = IMaverickFactory(0xEb6625D65a0553c9dBc64449e56abFe519bd9c9B);
}
文件 4 的 10:IERC20.sol
pragma solidity ^0.8.0;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
文件 5 的 10:IERC20Permit.sol
pragma solidity ^0.8.0;
interface IERC20Permit {
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
function nonces(address owner) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
文件 6 的 10:IStaking.sol
pragma solidity ^0.8.0;
interface IStaking {
function rebase() external;
function stake(uint256) external;
function unstake(uint256) external;
function index() external view returns (uint256);
}
文件 7 的 10:Ownable.sol
pragma solidity ^0.8.0;
import "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
_transferOwnership(_msgSender());
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 8 的 10:SafeERC20.sol
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";
library SafeERC20 {
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 {
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 oldAllowance = token.allowance(address(this), spender);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
}
}
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
function _callOptionalReturn(IERC20 token, bytes memory data) private {
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
(bool success, bytes memory returndata) = address(token).call(data);
return
success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
}
}
文件 9 的 10:SpiralRouter.sol
pragma solidity 0.8.16;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {
IStaking,
IERC20,
IVirtualRebaseViewer,
IBalancerVault,
IBalancerPool,
ICurveZap,
ICurvePool,
IMaverickPool,
IMaverickFactory,
RouterConstants
} from "./Helper.sol";
contract SpiralRouterV2 {
using SafeERC20 for IERC20;
constructor() {
_refreshAllowances();
}
function refreshAllowances() external {
_refreshAllowances();
}
function _refreshAllowances() internal {
IERC20(RouterConstants.coil).forceApprove(address(RouterConstants.staking), type(uint256).max);
IERC20(RouterConstants.spiral).forceApprove(address(RouterConstants.staking), type(uint256).max);
IERC20(RouterConstants.coil).forceApprove(address(RouterConstants.balVault), type(uint256).max);
IERC20(RouterConstants.usdc).forceApprove(address(RouterConstants.balVault), type(uint256).max);
IERC20(RouterConstants.coil).forceApprove(address(RouterConstants.curvePool), type(uint256).max);
IERC20(RouterConstants.usdc).forceApprove(address(RouterConstants.curvePool), type(uint256).max);
IERC20(RouterConstants.coil).forceApprove(address(RouterConstants.curveZap), type(uint256).max);
IERC20(RouterConstants.usdc).forceApprove(address(RouterConstants.curveZap), type(uint256).max);
IERC20(RouterConstants.coil).forceApprove(address(RouterConstants.maverickPool), type(uint256).max);
IERC20(RouterConstants.usdc).forceApprove(address(RouterConstants.maverickPool), type(uint256).max);
}
function swap(address tokenIn, address tokenOut, uint256[3] calldata amounts, uint256 minAmountOut) external {
uint256 amountIn = amounts[0] + amounts[1] + amounts[2];
uint256 amountCurve = amounts[0];
uint256 amountBalancer = amounts[1];
uint256 amountMaverick = amounts[2];
IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
if (tokenIn == address(RouterConstants.spiral)) {
RouterConstants.staking.unstake(amountIn);
uint256 index_ = RouterConstants.staking.index();
tokenIn = address(RouterConstants.coil);
amountIn = RouterConstants.coil.balanceOf(address(this));
amountCurve = amountCurve * index_ / 1e18;
amountBalancer = amountBalancer * index_ / 1e18;
amountMaverick = amountMaverick * index_ / 1e18;
}
address tokenOut_ = tokenOut;
if (tokenOut == address(RouterConstants.spiral)) {
tokenOut_ = address(RouterConstants.coil);
}
uint256 balanceBefore = IERC20(tokenOut_).balanceOf(address(this));
if (amountCurve > 0) {
curveSwap(IERC20(tokenIn), IERC20(tokenOut_), amountCurve);
}
if (amountBalancer > 0) {
balancerSwap(tokenIn, tokenOut_, amountBalancer);
}
if (amountMaverick > 0) {
maverickSwap(tokenIn, tokenOut_, IERC20(tokenIn).balanceOf(address(this)));
}
uint256 actualOut = IERC20(tokenOut_).balanceOf(address(this)) - balanceBefore;
if (tokenOut == address(RouterConstants.spiral)) {
RouterConstants.staking.stake(actualOut);
require(RouterConstants.spiral.balanceOf(address(this)) >= minAmountOut, "slippage");
RouterConstants.spiral.safeTransfer(msg.sender, RouterConstants.spiral.balanceOf(address(this)));
} else {
require(actualOut >= minAmountOut, "slippage");
IERC20(tokenOut).safeTransfer(msg.sender, actualOut);
}
}
function curveSwap(IERC20 tokenIn, IERC20 tokenOut, uint256 amountIn) internal {
ICurvePool pool = ICurvePool(RouterConstants.curvePool);
uint256 i;
uint256 j;
if (address(tokenIn) == address(RouterConstants.coil)) {
i = 0;
j = 2;
} else {
i = 2;
j = 0;
}
RouterConstants.curveZap.exchange(address(pool), i, j, amountIn, 0);
}
function balancerSwap(address tokenIn, address tokenOut, uint256 amountIn) internal {
RouterConstants.balVault.swap(
IBalancerVault.SingleSwap(
RouterConstants.balancerPoolId,
IBalancerVault.SwapKind.GIVEN_IN,
tokenIn,
tokenOut,
amountIn,
new bytes(0)
),
IBalancerVault.FundManagement(address(this), false, payable(address(this)), false),
0,
block.timestamp
);
}
function maverickSwap(address tokenIn, address tokenOut, uint256 amount) internal {
IMaverickPool pool = RouterConstants.maverickPool;
pool.swap(
address(this),
amount,
tokenIn < tokenOut,
false,
0,
abi.encode(
IMaverickPool.SwapCallbackData({
path: abi.encodePacked(tokenIn, address(pool), tokenOut),
payer: address(this),
exactOutput: false
})
)
);
}
function swapCallback(uint256 amountToPay, uint256 amountOut, bytes calldata _data) external {
require(amountToPay > 0 && amountOut > 0);
require(RouterConstants.maverickFactory.isFactoryPool(msg.sender));
IMaverickPool.SwapCallbackData memory data = abi.decode(_data, (IMaverickPool.SwapCallbackData));
bytes memory path = data.path;
address tokenToPay;
address pool;
assembly ("memory-safe") {
tokenToPay := div(mload(add(add(path, 0x20), 0)), 0x1000000000000000000000000)
pool := div(mload(add(add(path, 0x34), 0)), 0x1000000000000000000000000)
}
require(msg.sender == pool);
IERC20(tokenToPay).safeTransfer(pool, amountToPay);
}
}
文件 10 的 10:VirtualRebaseViewer.sol
pragma solidity 0.8.16;
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
interface IStaking {
struct Epoch {
uint256 length;
uint256 number;
uint256 endBlock;
uint256 apr;
}
function epoch() external view returns (Epoch memory);
function index() external view returns (uint256);
}
interface IVirtualRebaseViewer {
function epoch() external view returns (IStaking.Epoch memory);
function nextRebaseAt() external view returns (uint256);
function pendingIndex() external view returns (uint256, uint256);
function pendingCoil() external view returns (uint256);
function allInOne() external view returns (uint256, uint256, uint256, uint256);
}
contract VirtualRebaseViewer is IVirtualRebaseViewer {
address private constant staking = address(0x6701E792b7CD344BaE763F27099eEb314A4b4943);
address private constant spiral = address(0x85b6ACaBa696B9E4247175274F8263F99b4B9180);
address private constant coil = address(0x823E1B82cE1Dc147Bbdb25a203f046aFab1CE918);
uint256 private constant initialIndex = 10 ** 18;
uint256 private constant blocksPerYear = 2628000;
uint256 private constant aprBase = 10000;
constructor() {}
function epoch() external view returns (IStaking.Epoch memory) {
return IStaking(staking).epoch();
}
function nextRebaseAt() external view returns (uint256) {
(,, uint256 endBlock,) = _virtualRebase();
return endBlock;
}
function pendingIndex() external view returns (uint256, uint256) {
(uint256 pendingRebasesCount, uint256 futureIndex,,) = _virtualRebase();
return (pendingRebasesCount, futureIndex);
}
function pendingCoil() external view returns (uint256) {
(,,, uint256 stakedCoil) = _virtualRebase();
return stakedCoil;
}
function allInOne() external view returns (uint256, uint256, uint256, uint256) {
return _virtualRebase();
}
function _virtualRebase()
internal
view
returns (uint256 pendingRebasesCount, uint256 futureIndex, uint256 endBlock, uint256 stakedCoil)
{
IStaking.Epoch memory epoch = IStaking(staking).epoch();
futureIndex = IStaking(staking).index();
stakedCoil = IERC20(coil).balanceOf(staking);
uint256 totalSpiral = IERC20(spiral).totalSupply();
while (epoch.endBlock <= block.number) {
pendingRebasesCount++;
futureIndex = futureIndex + ((epoch.apr * futureIndex * epoch.length) / blocksPerYear / aprBase);
epoch.endBlock = epoch.endBlock + epoch.length;
epoch.number++;
if (totalSpiral > 0 && stakedCoil > 0 && epoch.apr > 0) {
uint256 mintAmount = (totalSpiral * futureIndex / initialIndex) - stakedCoil;
stakedCoil += mintAmount;
}
}
return (pendingRebasesCount, futureIndex, epoch.endBlock, stakedCoil);
}
}
{
"compilationTarget": {
"src/Contracts/SpiralRouter/SpiralRouter.sol": "SpiralRouterV2"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "none"
},
"optimizer": {
"enabled": true,
"runs": 999999
},
"remappings": [
":@/=lib/forge-std/src/",
":@openzeppelin/=lib/openzeppelin-contracts/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
":forge-std/=lib/forge-std/src/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":openzeppelin/=lib/openzeppelin-contracts/contracts/"
]
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"refreshAllowances","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint256[3]","name":"amounts","type":"uint256[3]"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"}],"name":"swap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountToPay","type":"uint256"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"swapCallback","outputs":[],"stateMutability":"nonpayable","type":"function"}]