fix(contracts): minor token related changes (#4580)

### Description

Minor token related changes like adding custom hook to 4626 collateral,
checking for ERC20 as valid contract in HypERC20Collateral, etc.

### Drive-by changes

check for overflow in bytes32ToAddress

### Related issues

- partly fixes
https://github.com/chainlight-io/2024-08-hyperlane/issues/14

### Backward compatibility

Yes

### Testing

Unit tests
pull/4743/head
Kunal Arora 4 weeks ago committed by GitHub
parent 8cc0d9a4ae
commit c55257cf5f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      .changeset/perfect-dryers-destroy.md
  2. 16
      solidity/contracts/client/GasRouter.sol
  3. 4
      solidity/contracts/libs/TypeCasts.sol
  4. 16
      solidity/contracts/token/HypERC20Collateral.sol
  5. 16
      solidity/contracts/token/extensions/HypERC4626Collateral.sol
  6. 2
      solidity/test/GasRouter.t.sol
  7. 2
      solidity/test/isms/RateLimitedIsm.t.sol
  8. 5
      solidity/test/token/HypERC20.t.sol
  9. 68
      solidity/test/token/HypERC4626Test.t.sol

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/core': minor
---
Minor token related changes like adding custom hook to 4626 collateral, checking for ERC20 as valid contract in HypERC20Collateral, etc.

@ -1,10 +1,25 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/
// ============ Internal Imports ============
import {Router} from "./Router.sol";
import {StandardHookMetadata} from "../hooks/libs/StandardHookMetadata.sol";
abstract contract GasRouter is Router {
event GasSet(uint32 domain, uint256 gas);
// ============ Mutable Storage ============
mapping(uint32 => uint256) public destinationGas;
@ -56,6 +71,7 @@ abstract contract GasRouter is Router {
function _setDestinationGas(uint32 domain, uint256 gas) internal {
destinationGas[domain] = gas;
emit GasSet(domain, gas);
}
function _GasRouter_dispatch(

@ -9,6 +9,10 @@ library TypeCasts {
// alignment preserving cast
function bytes32ToAddress(bytes32 _buf) internal pure returns (address) {
require(
uint256(_buf) <= uint256(type(uint160).max),
"TypeCasts: bytes32ToAddress overflow"
);
return address(uint160(uint256(_buf)));
}
}

@ -1,10 +1,25 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.0;
/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/
// ============ Internal Imports ============
import {TokenRouter} from "./libs/TokenRouter.sol";
import {TokenMessage} from "./libs/TokenMessage.sol";
import {MailboxClient} from "../client/MailboxClient.sol";
// ============ External Imports ============
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
@ -22,6 +37,7 @@ contract HypERC20Collateral is TokenRouter {
* @param erc20 Address of the token to keep as collateral
*/
constructor(address erc20, address _mailbox) TokenRouter(_mailbox) {
require(Address.isContract(erc20), "HypERC20Collateral: invalid token");
wrappedToken = IERC20(erc20);
}

@ -66,11 +66,7 @@ contract HypERC4626Collateral is HypERC20Collateral {
// Can't override _transferFromSender only because we need to pass shares in the token message
_transferFromSender(_amount);
uint256 _shares = _depositIntoVault(_amount);
uint256 _exchangeRate = PRECISION.mulDiv(
vault.totalAssets(),
vault.totalSupply(),
Math.Rounding.Down
);
uint256 _exchangeRate = vault.convertToAssets(PRECISION);
rateUpdateNonce++;
bytes memory _tokenMetadata = abi.encode(
@ -121,15 +117,19 @@ contract HypERC4626Collateral is HypERC20Collateral {
* @dev Update the exchange rate on the synthetic token by accounting for additional yield accrued to the underlying vault
* @param _destinationDomain domain of the vault
*/
function rebase(uint32 _destinationDomain) public payable {
function rebase(
uint32 _destinationDomain,
bytes calldata _hookMetadata,
address _hook
) public payable {
// force a rebase with an empty transfer to 0x1
_transferRemote(
_destinationDomain,
NULL_RECIPIENT,
0,
msg.value,
bytes(""),
address(0)
_hookMetadata,
_hook
);
}
}

@ -68,6 +68,8 @@ contract GasRouterTest is Test {
}
function testSetDestinationGas(uint256 gas) public {
vm.expectEmit(true, true, true, true);
emit GasRouter.GasSet(originDomain, gas);
setDestinationGas(remoteRouter, originDomain, gas);
assertEq(remoteRouter.destinationGas(originDomain), gas);

@ -75,7 +75,7 @@ contract RateLimitedIsmTest is Test {
TokenMessage.format(bytes32(""), _amount, bytes(""))
);
vm.expectRevert("InvalidRecipient");
vm.expectRevert("TypeCasts: bytes32ToAddress overflow");
rateLimitedIsm.verify(bytes(""), _message);
}

@ -406,6 +406,11 @@ contract HypERC20CollateralTest is HypTokenTest {
_enrollRemoteTokenRouter();
}
function test_constructor_revert_ifInvalidToken() public {
vm.expectRevert("HypERC20Collateral: invalid token");
new HypERC20Collateral(address(0), address(localMailbox));
}
function testInitialize_revert_ifAlreadyInitialized() public {}
function testRemoteTransfer() public {

@ -24,7 +24,9 @@ import {MockMailbox} from "../../contracts/mock/MockMailbox.sol";
import {HypERC20} from "../../contracts/token/HypERC20.sol";
import {HypERC4626Collateral} from "../../contracts/token/extensions/HypERC4626Collateral.sol";
import {HypERC4626} from "../../contracts/token/extensions/HypERC4626.sol";
import {StandardHookMetadata} from "../../contracts/hooks/libs/StandardHookMetadata.sol";
import "../../contracts/test/ERC4626/ERC4626Test.sol";
import {ProtocolFee} from "../../contracts/hooks/ProtocolFee.sol";
contract HypERC4626CollateralTest is HypTokenTest {
using TypeCasts for address;
@ -124,12 +126,36 @@ contract HypERC4626CollateralTest is HypTokenTest {
_accrueYield();
localRebasingToken.rebase(DESTINATION);
localRebasingToken.rebase(DESTINATION, bytes(""), address(0));
remoteMailbox.processNextInboundMessage();
assertEq(
assertApproxEqRelDecimal(
remoteToken.balanceOf(BOB),
transferAmount + _discountedYield()
transferAmount + _discountedYield(),
1e14,
0
);
}
function testRemoteTransfer_rebaseWithCustomHook() public {
_performRemoteTransferWithoutExpectation(0, transferAmount);
assertEq(remoteToken.balanceOf(BOB), transferAmount);
_accrueYield();
uint256 FEE = 1e18;
ProtocolFee customHook = new ProtocolFee(
FEE,
FEE,
address(this),
address(this)
);
localRebasingToken.rebase{value: FEE}(
DESTINATION,
StandardHookMetadata.overrideMsgValue(FEE),
address(customHook)
);
assertEq(address(customHook).balance, FEE);
}
function testRebaseWithTransfer() public {
@ -275,7 +301,7 @@ contract HypERC4626CollateralTest is HypTokenTest {
_accrueYield();
localRebasingToken.rebase(DESTINATION);
localRebasingToken.rebase(DESTINATION, bytes(""), address(0));
remoteMailbox.processNextInboundMessage();
// Use balance here since it might be off by <1bp
@ -314,7 +340,7 @@ contract HypERC4626CollateralTest is HypTokenTest {
_accrueYield();
_accrueYield(); // earning 2x yield to be split
localRebasingToken.rebase(DESTINATION);
localRebasingToken.rebase(DESTINATION, bytes(""), address(0));
vm.prank(CAROL);
remoteToken.transferRemote(
@ -352,7 +378,7 @@ contract HypERC4626CollateralTest is HypTokenTest {
// decrease collateral in vault by 10%
uint256 drawdown = 5e18;
primaryToken.burnFrom(address(vault), drawdown);
localRebasingToken.rebase(DESTINATION);
localRebasingToken.rebase(DESTINATION, bytes(""), address(0));
remoteMailbox.processNextInboundMessage();
// Use balance here since it might be off by <1bp
@ -378,7 +404,7 @@ contract HypERC4626CollateralTest is HypTokenTest {
_accrueYield();
localRebasingToken.rebase(DESTINATION);
localRebasingToken.rebase(DESTINATION, bytes(""), address(0));
remoteMailbox.processNextInboundMessage();
vm.prank(BOB);
@ -389,13 +415,23 @@ contract HypERC4626CollateralTest is HypTokenTest {
);
peerMailbox.processNextInboundMessage();
assertEq(remoteRebasingToken.exchangeRate(), 1045e7); // 5 * 0.9 = 4.5% yield
assertApproxEqRelDecimal(
remoteRebasingToken.exchangeRate(),
1045e7,
1e14,
0
); // 5 * 0.9 = 4.5% yield
assertEq(peerRebasingToken.exchangeRate(), 1e10); // assertingthat transfers by the synthetic variant don't impact the exchang rate
localRebasingToken.rebase(PEER_DESTINATION);
localRebasingToken.rebase(PEER_DESTINATION, bytes(""), address(0));
peerMailbox.processNextInboundMessage();
assertEq(peerRebasingToken.exchangeRate(), 1045e7); // asserting that the exchange rate is set finally by the collateral variant
assertApproxEqRelDecimal(
peerRebasingToken.exchangeRate(),
1045e7,
1e14,
0
); // asserting that the exchange rate is set finally by the collateral variant
}
function test_cyclicTransfers() public {
@ -405,7 +441,7 @@ contract HypERC4626CollateralTest is HypTokenTest {
_accrueYield();
localRebasingToken.rebase(DESTINATION); // yield is added
localRebasingToken.rebase(DESTINATION, bytes(""), address(0)); // yield is added
remoteMailbox.processNextInboundMessage();
// BOB: remote -> peer(BOB) (yield is leftover)
@ -417,7 +453,7 @@ contract HypERC4626CollateralTest is HypTokenTest {
);
peerMailbox.processNextInboundMessage();
localRebasingToken.rebase(PEER_DESTINATION);
localRebasingToken.rebase(PEER_DESTINATION, bytes(""), address(0));
peerMailbox.processNextInboundMessage();
// BOB: peer -> local(CAROL)
@ -457,11 +493,13 @@ contract HypERC4626CollateralTest is HypTokenTest {
_accrueYield();
localRebasingToken.rebase(DESTINATION);
localRebasingToken.rebase(DESTINATION, bytes(""), address(0));
remoteMailbox.processNextInboundMessage();
assertEq(
assertApproxEqRelDecimal(
remoteToken.balanceOf(BOB),
transferAmount + _discountedYield()
transferAmount + _discountedYield(),
1e14,
0
);
vm.prank(address(localMailbox));

Loading…
Cancel
Save