Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
95 changes: 95 additions & 0 deletions contracts/Gateway/INativeTokenGateway.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.25;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/**
* @title INativeTokenGateway
* @author Venus
* @notice Interface for NativeTokenGateway contract
*/
interface INativeTokenGateway {
/**
* @dev Emitted when native currency is supplied
*/
event TokensWrappedAndSupplied(address indexed sender, address indexed vToken, uint256 amount);

/**
* @dev Emitted when tokens are redeemed and then unwrapped to be sent to user
*/
event TokensRedeemedAndUnwrapped(address indexed sender, address indexed vToken, uint256 amount);

/**
* @dev Emitted when native tokens are borrowed and unwrapped
*/
event TokensBorrowedAndUnwrapped(address indexed sender, address indexed vToken, uint256 amount);

/**
* @dev Emitted when native currency is wrapped and repaid
*/
event TokensWrappedAndRepaid(address indexed sender, address indexed vToken, uint256 amount);

/**
* @dev Emitted when token is swept from the contract
*/
event SweepToken(address indexed token, address indexed receiver, uint256 amount);

/**
* @dev Emitted when native asset is swept from the contract
*/
event SweepNative(address indexed receiver, uint256 amount);

/**
* @notice Thrown if transfer of native token fails
*/
error NativeTokenTransferFailed();

/**
* @notice Thrown if the supplied address is a zero address where it is not allowed
*/
error ZeroAddressNotAllowed();

/**
* @notice Thrown if the supplied value is 0 where it is not allowed
*/
error ZeroValueNotAllowed();

/**
* @dev Wrap Native Token, get wNativeToken, mint vWNativeTokens, and supply to the market
* @param minter The address on behalf of whom the supply is performed
*/
function wrapAndSupply(address minter) external payable;

/**
* @dev Redeem vWNativeTokens, unwrap to Native Token, and send to the user
* @param redeemAmount The amount of underlying tokens to redeem
*/
function redeemUnderlyingAndUnwrap(uint256 redeemAmount) external;

/**
* @dev Redeem vWNativeTokens, unwrap to Native Token, and send to the user
* @param redeemTokens The amount of vWNative tokens to redeem
*/
function redeemAndUnwrap(uint256 redeemTokens) external;

/**
* @dev Borrow wNativeToken, unwrap to Native Token, and send to the user
* @param amount The amount of underlying tokens to borrow
*/
function borrowAndUnwrap(uint256 amount) external;

/**
* @dev Wrap Native Token, repay borrow in the market, and send remaining Native Token to the user
*/
function wrapAndRepay() external payable;

/**
* @dev Sweeps input token address tokens from the contract and sends them to the owner
*/
function sweepToken(IERC20 token) external;

/**
* @dev Sweeps native assets (Native Token) from the contract and sends them to the owner
*/
function sweepNative() external;
}
24 changes: 24 additions & 0 deletions contracts/Gateway/Interfaces/IVToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.25;

interface IVToken {
function mintBehalf(address receiver, uint256 mintAmount) external returns (uint256);

function redeemUnderlyingBehalf(address redeemer, uint256 redeemAmount) external returns (uint256);

function redeemBehalf(address redeemer, uint256 redeemTokens) external returns (uint256);

function repayBorrowBehalf(address borrower, uint256 repayAmount) external returns (uint256);

function borrowBehalf(address borrower, uint256 borrowAmount) external returns (uint256);

function borrowBalanceCurrent(address account) external returns (uint256);

function underlying() external returns (address);

function exchangeRateCurrent() external returns (uint256);

function transferFrom(address from, address to, uint256 amount) external returns (bool);

function redeem(uint256 redeemTokens) external returns (uint256);
}
16 changes: 16 additions & 0 deletions contracts/Gateway/Interfaces/IWrappedNative.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.25;

interface IWrappedNative {
function deposit() external payable;

function withdraw(uint256) external;

function approve(address guy, uint256 wad) external returns (bool);

function transferFrom(address src, address dst, uint256 wad) external returns (bool);

function transfer(address dst, uint256 wad) external returns (bool);

function balanceOf(address account) external view returns (uint256);
}
258 changes: 258 additions & 0 deletions contracts/Gateway/NativeTokenGateway.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;

import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";
import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol";

import { IWrappedNative } from "./Interfaces/IWrappedNative.sol";
import { INativeTokenGateway } from "./INativeTokenGateway.sol";
import { IVToken } from "./Interfaces/IVToken.sol";

/**
* @title NativeTokenGateway
* @author Venus
* @notice NativeTokenGateway contract facilitates interactions with a vToken market for native tokens (Native or wNativeToken)
*/
contract NativeTokenGateway is INativeTokenGateway, Ownable2Step, ReentrancyGuard {
using SafeERC20 for IERC20;

/// @notice Error code representing no errors in Venus operations
uint256 internal constant NO_ERROR = 0;

/**
* @notice Address of wrapped native token contract
*/
IWrappedNative public immutable wNativeToken;

Check warning on line 26 in contracts/Gateway/NativeTokenGateway.sol

View workflow job for this annotation

GitHub Actions / Lint

Immutable variables name are set to be in capitalized SNAKE_CASE

/**
* @notice Address of wrapped native token market
*/
IVToken public immutable vWNativeToken;

Check warning on line 31 in contracts/Gateway/NativeTokenGateway.sol

View workflow job for this annotation

GitHub Actions / Lint

Immutable variables name are set to be in capitalized SNAKE_CASE

/// @custom:error RedeemFailed
error RedeemFailed(uint256 err);

/// @custom:error BorrowFailed
error BorrowFailed(uint256 err);

/// @custom:error MintFailed
error MintFailed(uint256 err);

/// @custom:error RepayFailed
error RepayFailed(uint256 err);

/**
* @notice Constructor for NativeTokenGateway
* @param vWrappedNativeToken Address of wrapped native token market
*/
constructor(IVToken vWrappedNativeToken) {
ensureNonzeroAddress(address(vWrappedNativeToken));

vWNativeToken = vWrappedNativeToken;
wNativeToken = IWrappedNative(vWNativeToken.underlying());
}

/**
* @notice To receive Native when msg.data is empty
*/
receive() external payable {}

/**
* @notice To receive Native when msg.data is not empty
*/
fallback() external payable {}

/**
* @notice Wrap Native, get wNativeToken, mint vWNativeToken, and supply to the market.
* @param minter The address on behalf of whom the supply is performed.
* @custom:error ZeroAddressNotAllowed is thrown if address of minter is zero address
* @custom:error ZeroValueNotAllowed is thrown if mintAmount is zero
* @custom:error MintFailed is thrown if the mint operation fails
* @custom:event TokensWrappedAndSupplied is emitted when assets are supplied to the market
*/
function wrapAndSupply(address minter) external payable nonReentrant {
ensureNonzeroAddress(minter);

uint256 mintAmount = msg.value;
ensureNonzeroValue(mintAmount);

wNativeToken.deposit{ value: mintAmount }();
IERC20(address(wNativeToken)).forceApprove(address(vWNativeToken), mintAmount);

uint256 err = vWNativeToken.mintBehalf(minter, mintAmount);
if (err != NO_ERROR) revert MintFailed(err);

IERC20(address(wNativeToken)).forceApprove(address(vWNativeToken), 0);
emit TokensWrappedAndSupplied(minter, address(vWNativeToken), mintAmount);
}

/**
* @notice Redeem vWNativeToken, unwrap to Native Token, and send to the user
* @param redeemAmount The amount of underlying tokens to redeem
* @custom:error ZeroValueNotAllowed is thrown if redeemAmount is zero
* @custom:error RedeemFailed is thrown if the redeem operation fails
* @custom:event TokensRedeemedAndUnwrapped is emitted when assets are redeemed from a market and unwrapped
*/
function redeemUnderlyingAndUnwrap(uint256 redeemAmount) external nonReentrant {
_redeemAndUnwrap(redeemAmount, true);
}

/**
* @notice Redeem vWNativeToken, unwrap to Native Token, and send to the user
* @param redeemTokens The amount of vWNative tokens to redeem
* @custom:error ZeroValueNotAllowed is thrown if redeemTokens is zero
* @custom:error RedeemFailed is thrown if the redeem operation fails
* @custom:event TokensRedeemedAndUnwrapped is emitted when assets are redeemed from a market and unwrapped
*/
function redeemAndUnwrap(uint256 redeemTokens) external nonReentrant {
_redeemAndUnwrap(redeemTokens, false);
}

/**
* @dev Borrow wNativeToken, unwrap to Native, and send to the user
* @param borrowAmount The amount of underlying tokens to borrow
* @custom:error ZeroValueNotAllowed is thrown if borrowAmount is zero
* @custom:error BorrowFailed is thrown if the borrow operation fails
* @custom:event TokensBorrowedAndUnwrapped is emitted when assets are borrowed from a market and unwrapped
*/
function borrowAndUnwrap(uint256 borrowAmount) external nonReentrant {
ensureNonzeroValue(borrowAmount);

uint256 err = vWNativeToken.borrowBehalf(msg.sender, borrowAmount);
if (err != NO_ERROR) revert BorrowFailed(err);

wNativeToken.withdraw(borrowAmount);
_safeTransferNativeTokens(msg.sender, borrowAmount);
emit TokensBorrowedAndUnwrapped(msg.sender, address(vWNativeToken), borrowAmount);
}

/**
* @notice Wrap Native, repay borrow in the market, and send remaining Native to the user
* @custom:error ZeroValueNotAllowed is thrown if repayAmount is zero
* @custom:error RepayFailed is thrown if the repay operation fails
* @custom:event TokensWrappedAndRepaid is emitted when assets are repaid to a market and unwrapped
*/
function wrapAndRepay() external payable nonReentrant {
uint256 repayAmount = msg.value;
ensureNonzeroValue(repayAmount);

wNativeToken.deposit{ value: repayAmount }();
IERC20(address(wNativeToken)).forceApprove(address(vWNativeToken), repayAmount);

uint256 borrowBalanceBefore = vWNativeToken.borrowBalanceCurrent(msg.sender);
uint256 err = vWNativeToken.repayBorrowBehalf(msg.sender, repayAmount);
if (err != NO_ERROR) revert RepayFailed(err);
uint256 borrowBalanceAfter = vWNativeToken.borrowBalanceCurrent(msg.sender);

IERC20(address(wNativeToken)).forceApprove(address(vWNativeToken), 0);

if (borrowBalanceAfter == 0 && (repayAmount > borrowBalanceBefore)) {
uint256 dust;
unchecked {
dust = repayAmount - borrowBalanceBefore;
}

wNativeToken.withdraw(dust);
_safeTransferNativeTokens(msg.sender, dust);
}
emit TokensWrappedAndRepaid(msg.sender, address(vWNativeToken), borrowBalanceBefore - borrowBalanceAfter);
}

/**
* @notice Sweeps native assets (Native) from the contract and sends them to the owner
* @custom:event SweepNative is emitted when assets are swept from the contract
* @custom:access Controlled by Governance
*/
function sweepNative() external onlyOwner {
uint256 balance = address(this).balance;

if (balance > 0) {
address owner_ = owner();
_safeTransferNativeTokens(owner_, balance);
emit SweepNative(owner_, balance);
}
}

/**
* @notice Sweeps the input token address tokens from the contract and sends them to the owner
* @param token Address of the token
* @custom:event SweepToken emits on success
* @custom:access Controlled by Governance
*/
function sweepToken(IERC20 token) external onlyOwner {
uint256 balance = token.balanceOf(address(this));

if (balance > 0) {
address owner_ = owner();
token.safeTransfer(owner_, balance);
emit SweepToken(address(token), owner_, balance);
}
}

/**
* @dev Redeems tokens, unwrap them to Native Token, and send to the user
* This function is internally called by `redeemUnderlyingAndUnwrap` and `redeemAndUnwrap`
* @param redeemTokens The amount of tokens to be redeemed. This can refer to either the underlying tokens directly or their equivalent vTokens
* @param isUnderlying A boolean flag indicating whether the redemption is for underlying tokens directly (`true`) or for their equivalent vTokens (`false`).
* @custom:error ZeroValueNotAllowed is thrown if redeemTokens is zero
* @custom:error RedeemFailed is thrown if the redeem operation fails
* @custom:event TokensRedeemedAndUnwrapped is emitted when assets are redeemed from a market and unwrapped
*/
function _redeemAndUnwrap(uint256 redeemTokens, bool isUnderlying) internal {
ensureNonzeroValue(redeemTokens);

uint256 balanceBefore = wNativeToken.balanceOf(address(this));

if (isUnderlying) {
uint256 err = vWNativeToken.redeemUnderlyingBehalf(msg.sender, redeemTokens);
if (err != NO_ERROR) revert RedeemFailed(err);
} else {
uint256 err = vWNativeToken.redeemBehalf(msg.sender, redeemTokens);
if (err != NO_ERROR) revert RedeemFailed(err);
}

uint256 balanceAfter = wNativeToken.balanceOf(address(this));
uint256 redeemedAmount = balanceAfter - balanceBefore;
wNativeToken.withdraw(redeemedAmount);

_safeTransferNativeTokens(msg.sender, redeemedAmount);
emit TokensRedeemedAndUnwrapped(msg.sender, address(vWNativeToken), redeemedAmount);
}

/**
* @dev transfer Native tokens to an address, revert if it fails
* @param to recipient of the transfer
* @param value the amount to send
* @custom:error NativeTokenTransferFailed is thrown if the Native token transfer fails
*/
function _safeTransferNativeTokens(address to, uint256 value) internal {
(bool success, ) = to.call{ value: value }(new bytes(0));

if (!success) {
revert NativeTokenTransferFailed();
}
}

/**
* @dev Checks if the provided address is nonzero, reverts otherwise
* @param address_ Address to check
* @custom:error ZeroAddressNotAllowed is thrown if the provided address is a zero address
**/
function ensureNonzeroAddress(address address_) internal pure {
if (address_ == address(0)) {
revert ZeroAddressNotAllowed();
}
}

/**
* @dev Checks if the provided value is nonzero, reverts otherwise
* @param value_ Value to check
* @custom:error ZeroValueNotAllowed is thrown if the provided value is 0
*/
function ensureNonzeroValue(uint256 value_) internal pure {
if (value_ == 0) {
revert ZeroValueNotAllowed();
}
}
}
Loading