forked from hyperlane-xyz/hyperlane-monorepo
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(contracts): add wrapped HypERC4626 for ease of defi use (hyperla…
…ne-xyz#4563) ### Description ### Drive-by changes None ### Related issues - closes https://github.com/chainlight-io/2024-08-hyperlane/issues/7 ### Backward compatibility Yes ### Testing Unit
- Loading branch information
1 parent
234a93f
commit fe5229f
Showing
4 changed files
with
239 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@hyperlane-xyz/core': patch | ||
--- | ||
|
||
Add wrapped HypERC4626 for easy defi use |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@hyperlane-xyz/core': minor | ||
--- | ||
|
||
Added WHypERC4626 as a wrapper for rebasing HypERC4626 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()); | ||
} | ||
} |