feat(contracts): add wrapped HypERC4626 for ease of defi use (#4563)
### Description ### Drive-by changes None ### Related issues - closes https://github.com/chainlight-io/2024-08-hyperlane/issues/7 ### Backward compatibility Yes ### Testing Unitpull/4743/head
parent
b585de0c53
commit
8cc0d9a4ae
@ -0,0 +1,5 @@ |
|||||||
|
--- |
||||||
|
'@hyperlane-xyz/core': patch |
||||||
|
--- |
||||||
|
|
||||||
|
Add wrapped HypERC4626 for easy defi use |
@ -0,0 +1,5 @@ |
|||||||
|
--- |
||||||
|
'@hyperlane-xyz/core': minor |
||||||
|
--- |
||||||
|
|
||||||
|
Added WHypERC4626 as a wrapper for rebasing HypERC4626 |
@ -0,0 +1,113 @@ |
|||||||
|
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||||
|
pragma solidity >=0.8.0; |
||||||
|
|
||||||
|
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; |
||||||
|
import {HypERC4626} from "./HypERC4626.sol"; |
||||||
|
import {PackageVersioned} from "../../PackageVersioned.sol"; |
||||||
|
|
||||||
|
/*@@@@@@@ @@@@@@@@@ |
||||||
|
@@@@@@@@@ @@@@@@@@@ |
||||||
|
@@@@@@@@@ @@@@@@@@@ |
||||||
|
@@@@@@@@@ @@@@@@@@@ |
||||||
|
@@@@@@@@@@@@@@@@@@@@@@@@@ |
||||||
|
@@@@@ HYPERLANE @@@@@@@ |
||||||
|
@@@@@@@@@@@@@@@@@@@@@@@@@ |
||||||
|
@@@@@@@@@ @@@@@@@@@ |
||||||
|
@@@@@@@@@ @@@@@@@@@ |
||||||
|
@@@@@@@@@ @@@@@@@@@ |
||||||
|
@@@@@@@@@ @@@@@@@@*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* @title WHypERC4626 |
||||||
|
* @author Abacus Works |
||||||
|
* @notice A wrapper for HypERC4626 that allows for wrapping and unwrapping of underlying rebasing tokens |
||||||
|
*/ |
||||||
|
contract WHypERC4626 is ERC20, PackageVersioned { |
||||||
|
HypERC4626 public immutable underlying; |
||||||
|
|
||||||
|
constructor( |
||||||
|
HypERC4626 _underlying, |
||||||
|
string memory name, |
||||||
|
string memory symbol |
||||||
|
) ERC20(name, symbol) { |
||||||
|
underlying = _underlying; |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* @notice Wraps an amount of underlying tokens into wrapped tokens |
||||||
|
* @param _underlyingAmount The amount of underlying tokens to wrap |
||||||
|
* @return The amount of wrapped tokens |
||||||
|
*/ |
||||||
|
function wrap(uint256 _underlyingAmount) external returns (uint256) { |
||||||
|
require( |
||||||
|
_underlyingAmount > 0, |
||||||
|
"WHypERC4626: wrap amount must be greater than 0" |
||||||
|
); |
||||||
|
uint256 wrappedAmount = underlying.assetsToShares(_underlyingAmount); |
||||||
|
_mint(msg.sender, wrappedAmount); |
||||||
|
underlying.transferFrom(msg.sender, address(this), _underlyingAmount); |
||||||
|
return wrappedAmount; |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* @notice Unwraps an amount of wrapped tokens into underlying tokens |
||||||
|
* @param _wrappedAmount The amount of wrapped tokens to unwrap |
||||||
|
* @return The amount of underlying tokens |
||||||
|
*/ |
||||||
|
function unwrap(uint256 _wrappedAmount) external returns (uint256) { |
||||||
|
require( |
||||||
|
_wrappedAmount > 0, |
||||||
|
"WHypERC4626: unwrap amount must be greater than 0" |
||||||
|
); |
||||||
|
uint256 underlyingAmount = underlying.sharesToAssets(_wrappedAmount); |
||||||
|
_burn(msg.sender, _wrappedAmount); |
||||||
|
underlying.transfer(msg.sender, underlyingAmount); |
||||||
|
return underlyingAmount; |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* @notice Gets the amount of wrapped tokens for a given amount of underlying tokens |
||||||
|
* @param _underlyingAmount The amount of underlying tokens |
||||||
|
* @return The amount of wrapped tokens |
||||||
|
*/ |
||||||
|
function getWrappedAmount( |
||||||
|
uint256 _underlyingAmount |
||||||
|
) external view returns (uint256) { |
||||||
|
return underlying.assetsToShares(_underlyingAmount); |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* @notice Gets the amount of underlying tokens for a given amount of wrapped tokens |
||||||
|
* @param _wrappedAmount The amount of wrapped tokens |
||||||
|
* @return The amount of underlying tokens |
||||||
|
*/ |
||||||
|
function getUnderlyingAmount( |
||||||
|
uint256 _wrappedAmount |
||||||
|
) external view returns (uint256) { |
||||||
|
return underlying.sharesToAssets(_wrappedAmount); |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* @notice Gets the amount of wrapped tokens for 1 unit of underlying tokens |
||||||
|
* @return The amount of wrapped tokens |
||||||
|
*/ |
||||||
|
function wrappedPerUnderlying() external view returns (uint256) { |
||||||
|
return underlying.assetsToShares(1 * 10 ** underlying.decimals()); |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* @notice Gets the amount of underlying tokens for 1 unit of wrapped tokens |
||||||
|
* @return The amount of underlying tokens |
||||||
|
*/ |
||||||
|
function underlyingPerWrapped() external view returns (uint256) { |
||||||
|
return underlying.sharesToAssets(1 * 10 ** decimals()); |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* @notice Gets the decimals of the wrapped token |
||||||
|
* @return The decimals of the wrapped token |
||||||
|
*/ |
||||||
|
function decimals() public view override returns (uint8) { |
||||||
|
return underlying.decimals(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,116 @@ |
|||||||
|
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||||
|
pragma solidity ^0.8.13; |
||||||
|
|
||||||
|
import {Test} from "forge-std/Test.sol"; |
||||||
|
import {MockMailbox} from "../../contracts/mock/MockMailbox.sol"; |
||||||
|
import {WHypERC4626} from "../../contracts/token/extensions/WHypERC4626.sol"; |
||||||
|
import {HypERC4626} from "../../contracts/token/extensions/HypERC4626.sol"; |
||||||
|
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; |
||||||
|
|
||||||
|
contract MockHypERC4626 is HypERC4626 { |
||||||
|
constructor(address _mailbox) HypERC4626(18, _mailbox, 2) {} |
||||||
|
|
||||||
|
function mint(address to, uint256 amount) external { |
||||||
|
_mint(to, amount); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
contract WHypERC4626Test is Test { |
||||||
|
WHypERC4626 public wHypERC4626; |
||||||
|
MockHypERC4626 public underlyingToken; |
||||||
|
address public alice = address(0x1); |
||||||
|
address public bob = address(0x2); |
||||||
|
|
||||||
|
function setUp() public { |
||||||
|
MockMailbox mailbox = new MockMailbox(1); |
||||||
|
underlyingToken = new MockHypERC4626(address(mailbox)); |
||||||
|
wHypERC4626 = new WHypERC4626( |
||||||
|
underlyingToken, |
||||||
|
"Wrapped Rebasing Token", |
||||||
|
"WRT" |
||||||
|
); |
||||||
|
|
||||||
|
underlyingToken.mint(alice, 1000 * 10 ** 18); |
||||||
|
underlyingToken.mint(bob, 1000 * 10 ** 18); |
||||||
|
} |
||||||
|
|
||||||
|
function test_wrap() public { |
||||||
|
uint256 amount = 100 * 10 ** 18; |
||||||
|
|
||||||
|
vm.startPrank(alice); |
||||||
|
underlyingToken.approve(address(wHypERC4626), amount); |
||||||
|
uint256 wrappedAmount = wHypERC4626.wrap(amount); |
||||||
|
|
||||||
|
assertEq(wHypERC4626.balanceOf(alice), wrappedAmount); |
||||||
|
assertEq(underlyingToken.balanceOf(alice), 900 * 10 ** 18); |
||||||
|
vm.stopPrank(); |
||||||
|
} |
||||||
|
|
||||||
|
function test_wrap_revertsWhen_zeroAmount() public { |
||||||
|
vm.startPrank(alice); |
||||||
|
underlyingToken.approve(address(wHypERC4626), 0); |
||||||
|
vm.expectRevert("WHypERC4626: wrap amount must be greater than 0"); |
||||||
|
wHypERC4626.wrap(0); |
||||||
|
vm.stopPrank(); |
||||||
|
} |
||||||
|
|
||||||
|
function test_unwrap() public { |
||||||
|
uint256 amount = 100 * 10 ** 18; |
||||||
|
|
||||||
|
vm.startPrank(alice); |
||||||
|
underlyingToken.approve(address(wHypERC4626), amount); |
||||||
|
uint256 wrappedAmount = wHypERC4626.wrap(amount); |
||||||
|
|
||||||
|
uint256 unwrappedAmount = wHypERC4626.unwrap(wrappedAmount); |
||||||
|
|
||||||
|
assertEq(wHypERC4626.balanceOf(alice), 0); |
||||||
|
assertEq(underlyingToken.balanceOf(alice), 1000 * 10 ** 18); |
||||||
|
assertEq(unwrappedAmount, amount); |
||||||
|
vm.stopPrank(); |
||||||
|
} |
||||||
|
|
||||||
|
function test_unwrap_revertsWhen_zeroAmount() public { |
||||||
|
vm.startPrank(alice); |
||||||
|
vm.expectRevert("WHypERC4626: unwrap amount must be greater than 0"); |
||||||
|
wHypERC4626.unwrap(0); |
||||||
|
vm.stopPrank(); |
||||||
|
} |
||||||
|
|
||||||
|
function test_getWrappedAmount() public view { |
||||||
|
uint256 amount = 100 * 10 ** 18; |
||||||
|
uint256 wrappedAmount = wHypERC4626.getWrappedAmount(amount); |
||||||
|
|
||||||
|
assertEq(wrappedAmount, underlyingToken.assetsToShares(amount)); |
||||||
|
} |
||||||
|
|
||||||
|
function test_getUnderlyingAmount() public view { |
||||||
|
uint256 amount = 100 * 10 ** 18; |
||||||
|
uint256 underlyingAmount = wHypERC4626.getUnderlyingAmount(amount); |
||||||
|
|
||||||
|
assertEq(underlyingAmount, underlyingToken.sharesToAssets(amount)); |
||||||
|
} |
||||||
|
|
||||||
|
function test_wrappedPerUnderlying() public view { |
||||||
|
uint256 wrappedPerUnderlying = wHypERC4626.wrappedPerUnderlying(); |
||||||
|
|
||||||
|
assertEq( |
||||||
|
wrappedPerUnderlying, |
||||||
|
underlyingToken.assetsToShares(1 * 10 ** underlyingToken.decimals()) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
function test_underlyingPerWrapped() public view { |
||||||
|
uint256 underlyingPerWrapped = wHypERC4626.underlyingPerWrapped(); |
||||||
|
|
||||||
|
assertEq( |
||||||
|
underlyingPerWrapped, |
||||||
|
underlyingToken.sharesToAssets(1 * 10 ** underlyingToken.decimals()) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
function test_decimals() public view { |
||||||
|
uint8 decimals = wHypERC4626.decimals(); |
||||||
|
|
||||||
|
assertEq(decimals, underlyingToken.decimals()); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue