feat: support xerc20 and xerc20Lockbox in CLI (#3753)

### Description

- Supports xERC20 and xERC20Lockbox in CLI and UI

### Drive-by changes

- Rename XERC20Collateral to XERC20

### Backward compatibility

- Yes

### Testing

- Manual
pull/3854/head
Yorke Rhodes 6 months ago committed by GitHub
parent b6b26e2bb8
commit babe816f86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      .changeset/perfect-seahorses-add.md
  2. 1
      solidity/.solhint.json
  3. 1
      solidity/.solhintignore
  4. 12
      solidity/contracts/test/ERC20Test.sol
  5. 2
      solidity/contracts/token/extensions/HypFiatToken.sol
  6. 2
      solidity/contracts/token/extensions/HypXERC20.sol
  7. 54
      solidity/contracts/token/extensions/HypXERC20Lockbox.sol
  8. 15
      solidity/contracts/token/interfaces/IXERC20.sol
  9. 61
      solidity/contracts/token/interfaces/IXERC20Lockbox.sol
  10. 22
      solidity/test/token/HypERC20.t.sol
  11. 7
      typescript/cli/src/config/warp.ts
  12. 27
      typescript/sdk/src/token/Token.test.ts
  13. 7
      typescript/sdk/src/token/Token.ts
  14. 17
      typescript/sdk/src/token/TokenStandard.ts
  15. 3
      typescript/sdk/src/token/config.ts
  16. 15
      typescript/sdk/src/token/contracts.ts
  17. 7
      typescript/sdk/src/token/schemas.ts

@ -0,0 +1,7 @@
---
'@hyperlane-xyz/cli': minor
'@hyperlane-xyz/sdk': minor
'@hyperlane-xyz/core': minor
---
Support xERC20 and xERC20 Lockbox in SDK and CLI

@ -11,7 +11,6 @@
"func-name-mixedcase": "off", "func-name-mixedcase": "off",
"reason-string": ["warn",{"maxLength":64}], "reason-string": ["warn",{"maxLength":64}],
"prettier/prettier": "error", "prettier/prettier": "error",
"custom-errors": "off",
"gas-custom-errors": "off" "gas-custom-errors": "off"
}, },
"plugins": ["prettier"] "plugins": ["prettier"]

@ -1,2 +1,3 @@
contracts/mock contracts/mock
contracts/test contracts/test
contracts/interfaces/avs/vendored

@ -65,4 +65,16 @@ contract XERC20Test is ERC20Test, IXERC20 {
function burn(address account, uint256 amount) public override { function burn(address account, uint256 amount) public override {
_burn(account, amount); _burn(account, amount);
} }
function setLimits(
address _bridge,
uint256 _mintingLimit,
uint256 _burningLimit
) external {
require(false);
}
function owner() external returns (address) {
return address(0x0);
}
} }

@ -5,7 +5,7 @@ import {IFiatToken} from "../interfaces/IFiatToken.sol";
import {HypERC20Collateral} from "../HypERC20Collateral.sol"; import {HypERC20Collateral} from "../HypERC20Collateral.sol";
// see https://github.com/circlefin/stablecoin-evm/blob/master/doc/tokendesign.md#issuing-and-destroying-tokens // see https://github.com/circlefin/stablecoin-evm/blob/master/doc/tokendesign.md#issuing-and-destroying-tokens
contract HypFiatTokenCollateral is HypERC20Collateral { contract HypFiatToken is HypERC20Collateral {
constructor( constructor(
address _fiatToken, address _fiatToken,
address _mailbox address _mailbox

@ -4,7 +4,7 @@ pragma solidity >=0.8.0;
import {IXERC20} from "../interfaces/IXERC20.sol"; import {IXERC20} from "../interfaces/IXERC20.sol";
import {HypERC20Collateral} from "../HypERC20Collateral.sol"; import {HypERC20Collateral} from "../HypERC20Collateral.sol";
contract HypXERC20Collateral is HypERC20Collateral { contract HypXERC20 is HypERC20Collateral {
constructor( constructor(
address _xerc20, address _xerc20,
address _mailbox address _mailbox

@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
import {IXERC20Lockbox} from "../interfaces/IXERC20Lockbox.sol";
import {IXERC20, IERC20} from "../interfaces/IXERC20.sol";
import {HypERC20Collateral} from "../HypERC20Collateral.sol";
contract HypXERC20Lockbox is HypERC20Collateral {
uint256 constant MAX_INT = 2 ** 256 - 1;
IXERC20Lockbox public immutable lockbox;
IXERC20 public immutable xERC20;
constructor(
address _lockbox,
address _mailbox
) HypERC20Collateral(address(IXERC20Lockbox(_lockbox).ERC20()), _mailbox) {
lockbox = IXERC20Lockbox(_lockbox);
xERC20 = lockbox.XERC20();
// grant infinite approvals to lockbox
require(
IERC20(wrappedToken).approve(_lockbox, MAX_INT),
"erc20 lockbox approve failed"
);
require(
xERC20.approve(_lockbox, MAX_INT),
"xerc20 lockbox approve failed"
);
}
function _transferFromSender(
uint256 _amount
) internal override returns (bytes memory) {
// transfer erc20 from sender
super._transferFromSender(_amount);
// convert erc20 to xERC20
lockbox.deposit(_amount);
// burn xERC20
xERC20.burn(address(this), _amount);
return bytes("");
}
function _transferTo(
address _recipient,
uint256 _amount,
bytes calldata /*metadata*/
) internal override {
// mint xERC20
xERC20.mint(address(this), _amount);
// convert xERC20 to erc20
lockbox.withdrawTo(_recipient, _amount);
}
}

@ -21,4 +21,19 @@ interface IXERC20 is IERC20 {
* @param _amount The amount of tokens being burned * @param _amount The amount of tokens being burned
*/ */
function burn(address _user, uint256 _amount) external; function burn(address _user, uint256 _amount) external;
/**
* @notice Updates the limits of any bridge
* @dev Can only be called by the owner
* @param _mintingLimit The updated minting limit we are setting to the bridge
* @param _burningLimit The updated burning limit we are setting to the bridge
* @param _bridge The address of the bridge we are setting the limits too
*/
function setLimits(
address _bridge,
uint256 _mintingLimit,
uint256 _burningLimit
) external;
function owner() external returns (address);
} }

@ -0,0 +1,61 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.4 <0.9.0;
// adapted from https://github.com/defi-wonderland/xERC20
import {IXERC20} from "./IXERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IXERC20Lockbox {
/**
* @notice The XERC20 token of this contract
*/
function XERC20() external returns (IXERC20);
/**
* @notice The ERC20 token of this contract
*/
function ERC20() external returns (IERC20);
/**
* @notice Deposit ERC20 tokens into the lockbox
*
* @param _amount The amount of tokens to deposit
*/
function deposit(uint256 _amount) external;
/**
* @notice Deposit ERC20 tokens into the lockbox, and send the XERC20 to a user
*
* @param _user The user to send the XERC20 to
* @param _amount The amount of tokens to deposit
*/
function depositTo(address _user, uint256 _amount) external;
/**
* @notice Deposit the native asset into the lockbox, and send the XERC20 to a user
*
* @param _user The user to send the XERC20 to
*/
function depositNativeTo(address _user) external payable;
/**
* @notice Withdraw ERC20 tokens from the lockbox
*
* @param _amount The amount of tokens to withdraw
*/
function withdraw(uint256 _amount) external;
/**
* @notice Withdraw ERC20 tokens from the lockbox
*
* @param _user The user to withdraw to
* @param _amount The amount of tokens to withdraw
*/
function withdrawTo(address _user, uint256 _amount) external;
}

@ -28,8 +28,8 @@ import {HypERC20} from "../../contracts/token/HypERC20.sol";
import {HypERC20Collateral} from "../../contracts/token/HypERC20Collateral.sol"; import {HypERC20Collateral} from "../../contracts/token/HypERC20Collateral.sol";
import {IXERC20} from "../../contracts/token/interfaces/IXERC20.sol"; import {IXERC20} from "../../contracts/token/interfaces/IXERC20.sol";
import {IFiatToken} from "../../contracts/token/interfaces/IFiatToken.sol"; import {IFiatToken} from "../../contracts/token/interfaces/IFiatToken.sol";
import {HypXERC20Collateral} from "../../contracts/token/extensions/HypXERC20Collateral.sol"; import {HypXERC20} from "../../contracts/token/extensions/HypXERC20.sol";
import {HypFiatTokenCollateral} from "../../contracts/token/extensions/HypFiatTokenCollateral.sol"; import {HypFiatToken} from "../../contracts/token/extensions/HypFiatToken.sol";
import {HypNative} from "../../contracts/token/HypNative.sol"; import {HypNative} from "../../contracts/token/HypNative.sol";
import {TokenRouter} from "../../contracts/token/libs/TokenRouter.sol"; import {TokenRouter} from "../../contracts/token/libs/TokenRouter.sol";
import {TokenMessage} from "../../contracts/token/libs/TokenMessage.sol"; import {TokenMessage} from "../../contracts/token/libs/TokenMessage.sol";
@ -394,20 +394,20 @@ contract HypERC20CollateralTest is HypTokenTest {
} }
} }
contract HypXERC20CollateralTest is HypTokenTest { contract HypXERC20Test is HypTokenTest {
using TypeCasts for address; using TypeCasts for address;
HypXERC20Collateral internal xerc20Collateral; HypXERC20 internal xerc20Collateral;
function setUp() public override { function setUp() public override {
super.setUp(); super.setUp();
primaryToken = new XERC20Test(NAME, SYMBOL, TOTAL_SUPPLY, DECIMALS); primaryToken = new XERC20Test(NAME, SYMBOL, TOTAL_SUPPLY, DECIMALS);
localToken = new HypXERC20Collateral( localToken = new HypXERC20(
address(primaryToken), address(primaryToken),
address(localMailbox) address(localMailbox)
); );
xerc20Collateral = HypXERC20Collateral(address(localToken)); xerc20Collateral = HypXERC20(address(localToken));
xerc20Collateral.enrollRemoteRouter( xerc20Collateral.enrollRemoteRouter(
DESTINATION, DESTINATION,
@ -442,22 +442,22 @@ contract HypXERC20CollateralTest is HypTokenTest {
} }
} }
contract HypFiatTokenCollateralTest is HypTokenTest { contract HypFiatTokenTest is HypTokenTest {
using TypeCasts for address; using TypeCasts for address;
HypFiatTokenCollateral internal fiatTokenCollateral; HypFiatToken internal fiatToken;
function setUp() public override { function setUp() public override {
super.setUp(); super.setUp();
primaryToken = new FiatTokenTest(NAME, SYMBOL, TOTAL_SUPPLY, DECIMALS); primaryToken = new FiatTokenTest(NAME, SYMBOL, TOTAL_SUPPLY, DECIMALS);
localToken = new HypFiatTokenCollateral( localToken = new HypFiatToken(
address(primaryToken), address(primaryToken),
address(localMailbox) address(localMailbox)
); );
fiatTokenCollateral = HypFiatTokenCollateral(address(localToken)); fiatToken = HypFiatToken(address(localToken));
fiatTokenCollateral.enrollRemoteRouter( fiatToken.enrollRemoteRouter(
DESTINATION, DESTINATION,
address(remoteToken).addressToBytes32() address(remoteToken).addressToBytes32()
); );

@ -29,8 +29,10 @@ const TYPE_DESCRIPTIONS: Record<TokenType, string> = {
'Extends an existing ERC4626 with remote transfer functionality', 'Extends an existing ERC4626 with remote transfer functionality',
[TokenType.collateralFiat]: [TokenType.collateralFiat]:
'Extends an existing FiatToken with remote transfer functionality', 'Extends an existing FiatToken with remote transfer functionality',
[TokenType.collateralXERC20]: [TokenType.XERC20]:
'Extends an existing xERC20 with Warp Route functionality', 'Extends an existing xERC20 with Warp Route functionality',
[TokenType.XERC20Lockbox]:
'Extends an existing xERC20 Lockbox with Warp Route functionality',
// TODO: describe // TODO: describe
[TokenType.fastSynthetic]: '', [TokenType.fastSynthetic]: '',
[TokenType.syntheticUri]: '', [TokenType.syntheticUri]: '',
@ -132,7 +134,8 @@ export async function createWarpRouteDeployConfig({
switch (type) { switch (type) {
case TokenType.collateral: case TokenType.collateral:
case TokenType.collateralXERC20: case TokenType.XERC20:
case TokenType.XERC20Lockbox:
case TokenType.collateralFiat: case TokenType.collateralFiat:
case TokenType.collateralUri: case TokenType.collateralUri:
case TokenType.fastCollateral: case TokenType.fastCollateral:

@ -47,33 +47,6 @@ const STANDARD_TO_TOKEN: Record<TokenStandard, TokenArgs | null> = {
symbol: 'USDC', symbol: 'USDC',
name: 'USDC', name: 'USDC',
}, },
[TokenStandard.EvmHypXERC20Collateral]: {
chainName: TestChainName.test3,
standard: TokenStandard.EvmHypXERC20Collateral,
addressOrDenom: '0x31b5234A896FbC4b3e2F7237592D054716762131',
collateralAddressOrDenom: '0x64544969ed7ebf5f083679233325356ebe738930',
decimals: 18,
symbol: 'USDC',
name: 'USDC',
},
[TokenStandard.EvmHypFiatCollateral]: {
chainName: TestChainName.test3,
standard: TokenStandard.EvmHypXERC20Collateral,
addressOrDenom: '0x31b5234A896FbC4b3e2F7237592D054716762131',
collateralAddressOrDenom: '0x64544969ed7ebf5f083679233325356ebe738930',
decimals: 18,
symbol: 'USDC',
name: 'USDC',
},
[TokenStandard.EvmHypCollateralVault]: {
chainName: TestChainName.test3,
standard: TokenStandard.EvmHypCollateral,
addressOrDenom: '0x31b5234A896FbC4b3e2F7237592D054716762131',
collateralAddressOrDenom: '0x64544969ed7ebf5f083679233325356ebe738930',
decimals: 18,
symbol: 'USDC',
name: 'USDC',
},
[TokenStandard.EvmHypSynthetic]: { [TokenStandard.EvmHypSynthetic]: {
chainName: TestChainName.test2, chainName: TestChainName.test2,
standard: TokenStandard.EvmHypSynthetic, standard: TokenStandard.EvmHypSynthetic,

@ -205,12 +205,7 @@ export class Token implements IToken {
return new EvmHypNativeAdapter(chainName, multiProvider, { return new EvmHypNativeAdapter(chainName, multiProvider, {
token: addressOrDenom, token: addressOrDenom,
}); });
} else if ( } else if (standard === TokenStandard.EvmHypCollateral) {
standard === TokenStandard.EvmHypCollateral ||
standard === TokenStandard.EvmHypCollateralVault ||
standard === TokenStandard.EvmHypXERC20Collateral ||
standard === TokenStandard.EvmHypFiatCollateral
) {
return new EvmHypCollateralAdapter(chainName, multiProvider, { return new EvmHypCollateralAdapter(chainName, multiProvider, {
token: addressOrDenom, token: addressOrDenom,
}); });

@ -14,9 +14,6 @@ export enum TokenStandard {
EvmNative = 'EvmNative', EvmNative = 'EvmNative',
EvmHypNative = 'EvmHypNative', EvmHypNative = 'EvmHypNative',
EvmHypCollateral = 'EvmHypCollateral', EvmHypCollateral = 'EvmHypCollateral',
EvmHypXERC20Collateral = 'EvmHypXERC20Collateral',
EvmHypFiatCollateral = 'EvmHypFiatCollateral',
EvmHypCollateralVault = 'EvmHypCollateralVault',
EvmHypSynthetic = 'EvmHypSynthetic', EvmHypSynthetic = 'EvmHypSynthetic',
// Sealevel (Solana) // Sealevel (Solana)
@ -50,10 +47,7 @@ export const TOKEN_STANDARD_TO_PROTOCOL: Record<TokenStandard, ProtocolType> = {
EvmNative: ProtocolType.Ethereum, EvmNative: ProtocolType.Ethereum,
EvmHypNative: ProtocolType.Ethereum, EvmHypNative: ProtocolType.Ethereum,
EvmHypCollateral: ProtocolType.Ethereum, EvmHypCollateral: ProtocolType.Ethereum,
EvmHypCollateralVault: ProtocolType.Ethereum,
EvmHypSynthetic: ProtocolType.Ethereum, EvmHypSynthetic: ProtocolType.Ethereum,
EvmHypXERC20Collateral: ProtocolType.Ethereum,
EvmHypFiatCollateral: ProtocolType.Ethereum,
// Sealevel (Solana) // Sealevel (Solana)
SealevelSpl: ProtocolType.Sealevel, SealevelSpl: ProtocolType.Sealevel,
@ -96,8 +90,6 @@ export const TOKEN_NFT_STANDARDS = [
export const TOKEN_COLLATERALIZED_STANDARDS = [ export const TOKEN_COLLATERALIZED_STANDARDS = [
TokenStandard.EvmHypCollateral, TokenStandard.EvmHypCollateral,
TokenStandard.EvmHypNative, TokenStandard.EvmHypNative,
TokenStandard.EvmHypXERC20Collateral,
TokenStandard.EvmHypFiatCollateral,
TokenStandard.SealevelHypCollateral, TokenStandard.SealevelHypCollateral,
TokenStandard.SealevelHypNative, TokenStandard.SealevelHypNative,
TokenStandard.CwHypCollateral, TokenStandard.CwHypCollateral,
@ -107,8 +99,6 @@ export const TOKEN_COLLATERALIZED_STANDARDS = [
export const TOKEN_HYP_STANDARDS = [ export const TOKEN_HYP_STANDARDS = [
TokenStandard.EvmHypNative, TokenStandard.EvmHypNative,
TokenStandard.EvmHypCollateral, TokenStandard.EvmHypCollateral,
TokenStandard.EvmHypXERC20Collateral,
TokenStandard.EvmHypFiatCollateral,
TokenStandard.EvmHypSynthetic, TokenStandard.EvmHypSynthetic,
TokenStandard.SealevelHypNative, TokenStandard.SealevelHypNative,
TokenStandard.SealevelHypCollateral, TokenStandard.SealevelHypCollateral,
@ -137,9 +127,10 @@ export const TOKEN_COSMWASM_STANDARDS = [
export const TOKEN_TYPE_TO_STANDARD: Record<TokenType, TokenStandard> = { export const TOKEN_TYPE_TO_STANDARD: Record<TokenType, TokenStandard> = {
[TokenType.native]: TokenStandard.EvmHypNative, [TokenType.native]: TokenStandard.EvmHypNative,
[TokenType.collateral]: TokenStandard.EvmHypCollateral, [TokenType.collateral]: TokenStandard.EvmHypCollateral,
[TokenType.collateralFiat]: TokenStandard.EvmHypFiatCollateral, [TokenType.collateralFiat]: TokenStandard.EvmHypCollateral,
[TokenType.collateralXERC20]: TokenStandard.EvmHypXERC20Collateral, [TokenType.XERC20]: TokenStandard.EvmHypCollateral,
[TokenType.collateralVault]: TokenStandard.EvmHypCollateralVault, [TokenType.XERC20Lockbox]: TokenStandard.EvmHypCollateral,
[TokenType.collateralVault]: TokenStandard.EvmHypCollateral,
[TokenType.collateralUri]: TokenStandard.EvmHypCollateral, [TokenType.collateralUri]: TokenStandard.EvmHypCollateral,
[TokenType.fastCollateral]: TokenStandard.EvmHypCollateral, [TokenType.fastCollateral]: TokenStandard.EvmHypCollateral,
[TokenType.synthetic]: TokenStandard.EvmHypSynthetic, [TokenType.synthetic]: TokenStandard.EvmHypSynthetic,

@ -4,7 +4,8 @@ export enum TokenType {
syntheticUri = 'syntheticUri', syntheticUri = 'syntheticUri',
collateral = 'collateral', collateral = 'collateral',
collateralVault = 'collateralVault', collateralVault = 'collateralVault',
collateralXERC20 = 'collateralXERC20', XERC20 = 'xERC20',
XERC20Lockbox = 'xERC20Lockbox',
collateralFiat = 'collateralFiat', collateralFiat = 'collateralFiat',
fastCollateral = 'fastCollateral', fastCollateral = 'fastCollateral',
collateralUri = 'collateralUri', collateralUri = 'collateralUri',

@ -8,10 +8,11 @@ import {
HypERC721URICollateral__factory, HypERC721URICollateral__factory,
HypERC721URIStorage__factory, HypERC721URIStorage__factory,
HypERC721__factory, HypERC721__factory,
HypFiatTokenCollateral__factory, HypFiatToken__factory,
HypNativeScaled__factory, HypNativeScaled__factory,
HypNative__factory, HypNative__factory,
HypXERC20Collateral__factory, HypXERC20Lockbox__factory,
HypXERC20__factory,
} from '@hyperlane-xyz/core'; } from '@hyperlane-xyz/core';
import { TokenType } from './config.js'; import { TokenType } from './config.js';
@ -21,8 +22,9 @@ export const hypERC20contracts = {
[TokenType.fastSynthetic]: 'FastHypERC20', [TokenType.fastSynthetic]: 'FastHypERC20',
[TokenType.synthetic]: 'HypERC20', [TokenType.synthetic]: 'HypERC20',
[TokenType.collateral]: 'HypERC20Collateral', [TokenType.collateral]: 'HypERC20Collateral',
[TokenType.collateralFiat]: 'HypFiatTokenCollateral', [TokenType.collateralFiat]: 'HypFiatToken',
[TokenType.collateralXERC20]: 'HypXERC20Collateral', [TokenType.XERC20]: 'HypXERC20',
[TokenType.XERC20Lockbox]: 'HypXERC20Lockbox',
[TokenType.collateralVault]: 'HypERC20CollateralVaultDeposit', [TokenType.collateralVault]: 'HypERC20CollateralVaultDeposit',
[TokenType.native]: 'HypNative', [TokenType.native]: 'HypNative',
[TokenType.nativeScaled]: 'HypNativeScaled', [TokenType.nativeScaled]: 'HypNativeScaled',
@ -35,8 +37,9 @@ export const hypERC20factories = {
[TokenType.synthetic]: new HypERC20__factory(), [TokenType.synthetic]: new HypERC20__factory(),
[TokenType.collateral]: new HypERC20Collateral__factory(), [TokenType.collateral]: new HypERC20Collateral__factory(),
[TokenType.collateralVault]: new HypERC20CollateralVaultDeposit__factory(), [TokenType.collateralVault]: new HypERC20CollateralVaultDeposit__factory(),
[TokenType.collateralFiat]: new HypFiatTokenCollateral__factory(), [TokenType.collateralFiat]: new HypFiatToken__factory(),
[TokenType.collateralXERC20]: new HypXERC20Collateral__factory(), [TokenType.XERC20]: new HypXERC20__factory(),
[TokenType.XERC20Lockbox]: new HypXERC20Lockbox__factory(),
[TokenType.native]: new HypNative__factory(), [TokenType.native]: new HypNative__factory(),
[TokenType.nativeScaled]: new HypNativeScaled__factory(), [TokenType.nativeScaled]: new HypNativeScaled__factory(),
}; };

@ -17,11 +17,12 @@ export const TokenMetadataSchema = z.object({
export const CollateralConfigSchema = TokenMetadataSchema.partial().extend({ export const CollateralConfigSchema = TokenMetadataSchema.partial().extend({
type: z.enum([ type: z.enum([
TokenType.collateral, TokenType.collateral,
TokenType.collateralXERC20, TokenType.collateralVault,
TokenType.XERC20,
TokenType.XERC20Lockbox,
TokenType.collateralFiat, TokenType.collateralFiat,
TokenType.collateralUri,
TokenType.fastCollateral, TokenType.fastCollateral,
TokenType.collateralVault, TokenType.collateralUri,
]), ]),
token: z token: z
.string() .string()

Loading…
Cancel
Save