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

Unit
pull/4743/head
Kunal Arora 6 days ago committed by GitHub
parent b585de0c53
commit 8cc0d9a4ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      .changeset/itchy-bananas-know.md
  2. 5
      .changeset/shiny-baboons-hunt.md
  3. 113
      solidity/contracts/token/extensions/WHypERC4626.sol
  4. 116
      solidity/test/token/WHypERC4626.t.sol

@ -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…
Cancel
Save