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