Contract Name:
ApeDelegate
Contract Source Code:
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.0;
import "./Ownable.sol";
/**
* @dev Contract module which provides access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership} and {acceptOwnership}.
*
* This module is used through inheritance. It will make available all functions
* from parent (Ownable).
*/
abstract contract Ownable2Step is Ownable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
/**
* @dev The new owner accepts the ownership transfer.
*/
function acceptOwnership() public virtual {
address sender = _msgSender();
require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner");
_transferOwnership(sender);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @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 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.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
/**
* @dev Interface for Arbitrum special l2 functions
*/
interface IArbInfo {
function configureAutomaticYield() external;
function configureVoidYield() external;
function configureDelegateYield(address delegate) external;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
/**
* @dev Interface for errors potentially used in all libraries (general names)
*/
interface IGeneralErrors {
error InitError();
error InvalidAddresses();
error InvalidAddress();
error InvalidInputLength();
error InvalidCollateralIndex();
error WrongParams();
error WrongLength();
error WrongOrder();
error WrongIndex();
error BlockOrder();
error Overflow();
error ZeroAddress();
error ZeroValue();
error AlreadyExists();
error DoesntExist();
error Paused();
error BelowMin();
error AboveMax();
error NotAuthorized();
error WrongTradeType();
error WrongOrderType();
error InsufficientBalance();
error UnsupportedChain();
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
/**
* @dev Interface for GToken contract
*/
interface IGToken {
struct GnsPriceProvider {
address addr;
bytes signature;
}
struct LockedDeposit {
address owner;
uint256 shares; // collateralConfig.precision
uint256 assetsDeposited; // collateralConfig.precision
uint256 assetsDiscount; // collateralConfig.precision
uint256 atTimestamp; // timestamp
uint256 lockDuration; // timestamp
}
struct ContractAddresses {
address asset;
address owner; // 2-week timelock contract
address manager; // 3-day timelock contract
address admin; // bypasses timelock, access to emergency functions
address gnsToken;
address lockedDepositNft;
address pnlHandler;
address openTradesPnlFeed;
GnsPriceProvider gnsPriceProvider;
}
struct Meta {
string name;
string symbol;
}
function manager() external view returns (address);
function admin() external view returns (address);
function currentEpoch() external view returns (uint256);
function currentEpochStart() external view returns (uint256);
function currentEpochPositiveOpenPnl() external view returns (uint256);
function updateAccPnlPerTokenUsed(
uint256 prevPositiveOpenPnl,
uint256 newPositiveOpenPnl
) external returns (uint256);
function getLockedDeposit(uint256 depositId) external view returns (LockedDeposit memory);
function sendAssets(uint256 assets, address receiver) external;
function receiveAssets(uint256 assets, address user) external;
function distributeReward(uint256 assets) external;
function tvl() external view returns (uint256);
function marketCap() external view returns (uint256);
function shareToAssetsPrice() external view returns (uint256);
function collateralConfig() external view returns (uint128, uint128);
event ManagerUpdated(address newValue);
event AdminUpdated(address newValue);
event PnlHandlerUpdated(address newValue);
event OpenTradesPnlFeedUpdated(address newValue);
event GnsPriceProviderUpdated(GnsPriceProvider newValue);
event WithdrawLockThresholdsPUpdated(uint256[2] newValue);
event MaxAccOpenPnlDeltaUpdated(uint256 newValue);
event MaxDailyAccPnlDeltaUpdated(uint256 newValue);
event MaxSupplyIncreaseDailyPUpdated(uint256 newValue);
event LossesBurnPUpdated(uint256 newValue);
event MaxGnsSupplyMintDailyPUpdated(uint256 newValue);
event MaxDiscountPUpdated(uint256 newValue);
event MaxDiscountThresholdPUpdated(uint256 newValue);
event CurrentMaxSupplyUpdated(uint256 newValue);
event DailyAccPnlDeltaReset();
event ShareToAssetsPriceUpdated(uint256 newValue);
event OpenTradesPnlFeedCallFailed();
event WithdrawRequested(
address indexed sender,
address indexed owner,
uint256 shares,
uint256 currEpoch,
uint256 indexed unlockEpoch
);
event WithdrawCanceled(
address indexed sender,
address indexed owner,
uint256 shares,
uint256 currEpoch,
uint256 indexed unlockEpoch
);
event DepositLocked(address indexed sender, address indexed owner, uint256 depositId, LockedDeposit d);
event DepositUnlocked(
address indexed sender,
address indexed receiver,
address indexed owner,
uint256 depositId,
LockedDeposit d
);
event RewardDistributed(address indexed sender, uint256 assets);
event AssetsSent(address indexed sender, address indexed receiver, uint256 assets);
event AssetsReceived(address indexed sender, address indexed user, uint256 assets, uint256 assetsLessDeplete);
event Depleted(address indexed sender, uint256 assets, uint256 amountGns);
event Refilled(address indexed sender, uint256 assets, uint256 amountGns);
event AccPnlPerTokenUsedUpdated(
address indexed sender,
uint256 indexed newEpoch,
uint256 prevPositiveOpenPnl,
uint256 newPositiveOpenPnl,
uint256 newEpochPositiveOpenPnl,
int256 newAccPnlPerTokenUsed
);
error OnlyManager();
error OnlyTradingPnlHandler();
error OnlyPnlFeed();
error AddressZero();
error PriceZero();
error ValueZero();
error BytesZero();
error NoActiveDiscount();
error BelowMin();
error AboveMax();
error WrongValue();
error WrongValues();
error GnsPriceCallFailed();
error GnsTokenPriceZero();
error PendingWithdrawal();
error EndOfEpoch();
error NotAllowed();
error NoDiscount();
error NotUnlocked();
error NotEnoughAssets();
error MaxDailyPnl();
error NotUnderCollateralized();
error AboveInflationLimit();
// Ownable
error OwnableInvalidOwner(address owner);
// ERC4626
error ERC4626ExceededMaxDeposit();
error ERC4626ExceededMaxMint();
error ERC4626ExceededMaxWithdraw();
error ERC4626ExceededMaxRedeem();
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
/**
* @dev Interface for WETH9 token
*/
interface IWETH9 {
function approve(address spender, uint256 amount) external returns (bool);
function transfer(address to, uint256 amount) external returns (bool);
function deposit() external payable;
function withdraw(uint256) external;
function balanceOf(address account) external view returns (uint256);
event Approval(address indexed src, address indexed guy, uint256 wad);
event Transfer(address indexed src, address indexed dst, uint256 wad);
event Deposit(address indexed dst, uint256 wad);
event Withdrawal(address indexed src, uint256 wad);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import "../interfaces/IArbInfo.sol";
/**
*
* @dev Library that handles delegating native yield. Only supports Arbitrum Orbit chains.
*/
library NativeYieldUtils {
address private constant ARB_INFO = address(101);
event Delegated(address newDelegate, bool success);
event AutomaticYieldSet(bool success);
/**
* @dev Sets the Native Yield delegate for this contract
* @param _delegate the new delegate. Setting to address(0) disables native yield
*
* Emits {Delegated} event
*/
function trySetDelegate(address _delegate) internal returns (bool success) {
bytes memory data;
if (_delegate == address(0)) {
// If `_delegate` is address(0) then disable yield
data = abi.encodeWithSelector(IArbInfo.configureVoidYield.selector);
} else {
// If `_delegate` is not address(0) then delegate the native yield
data = abi.encodeWithSelector(IArbInfo.configureDelegateYield.selector, _delegate);
}
(success, ) = ARB_INFO.call(data);
emit Delegated(_delegate, success);
}
/**
* @dev Sets the Native Yield mode to Automatic
*/
function trySetAutomaticYield() internal returns (bool success) {
(success, ) = ARB_INFO.call(abi.encodeWithSelector(IArbInfo.configureAutomaticYield.selector));
emit AutomaticYieldSet(success);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import "../interfaces/IGeneralErrors.sol";
import "../interfaces/IWETH9.sol";
import "../interfaces/IGToken.sol";
import "../libraries/NativeYieldUtils.sol";
/**
* @dev Contract used as Delegate for gTrade's custom wAPE. Receives the Native APE yield and distributes it to gAPE depositors.
*/
contract ApeDelegate is Ownable2Step {
uint256 public constant MIN_APE_WEI_AMOUNT = 0.01e18; // Minimum amount of APE needed to call `distributeYield`
IWETH9 public immutable wApe;
IGToken public gToken;
event GTokenUpdated(address gToken);
event YieldDistributed(uint256 amount);
constructor(IWETH9 _wApe, address _owner) {
if (address(_wApe) == address(0) || _owner == address(0)) revert IGeneralErrors.ZeroAddress();
// Set wAPE address
wApe = _wApe;
// Transfer ownership
_transferOwnership(_owner);
// Switch on Automatic Yield. This call gracefully fails for chains without ArbInfo and NativeYield
NativeYieldUtils.trySetAutomaticYield();
}
/**
* @dev Receive function. Allows contract to accept native token transfers
*/
receive() external payable {}
/**
* @dev Updated `gToken` address and approves wApe spending
* @param _gToken the new gToken address
*/
function setGToken(address _gToken) external onlyOwner {
if (_gToken == address(0)) revert IGeneralErrors.ZeroAddress();
// If gToken was previously set, revoke approval
if (address(gToken) != address(0)) wApe.approve(address(gToken), 0);
// Approve wApe spending by the new gToken address
wApe.approve(_gToken, type(uint256).max);
// Update state variable with new gToken address
gToken = IGToken(_gToken);
emit GTokenUpdated(_gToken);
}
/**
* @dev Distributes native yield available to gToken depositors
*/
function distributeYield() external {
// If gToken is not yet set then revert
if (address(gToken) == address(0)) revert IGeneralErrors.ZeroAddress();
uint256 balance = address(this).balance;
// Revert if there is not enough yield to distribute
if (balance < MIN_APE_WEI_AMOUNT) revert IGeneralErrors.BelowMin();
// Deposit native token to receive wApe
wApe.deposit{value: balance}();
// Distribute converted balance
gToken.distributeReward(balance);
emit YieldDistributed(balance);
}
}