Skip to content

Commit

Permalink
feat(sdk): XERC20 token adapter (#3911)
Browse files Browse the repository at this point in the history
### Description

`HypXERC20Adapter` that allows checking for mint and burn limits put in
place on XERC20 contracts.

### Drive-by changes

- Couple methods added to the `XERC20` interface
- Corrects `HypXERC20Lockbox` and `HypXERC20` config during CLI
deployments

### Related issues

- Fixes #3851

### Backward compatibility

Yes

### Testing

CLI testing.
  • Loading branch information
AlexBHarley authored Jun 14, 2024
1 parent 1284096 commit 51bfff6
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 9 deletions.
8 changes: 8 additions & 0 deletions .changeset/selfish-days-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@hyperlane-xyz/sdk': minor
'@hyperlane-xyz/core': minor
'@hyperlane-xyz/cli': minor
---

Mint/burn limit checking for xERC20 bridging
Corrects CLI output for HypXERC20 and HypXERC20Lockbox deployments
12 changes: 12 additions & 0 deletions solidity/contracts/test/ERC20Test.sol
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ contract XERC20Test is ERC20Test, IXERC20 {
function owner() external pure returns (address) {
return address(0x0);
}

function burningCurrentLimitOf(
address _bridge
) external view returns (uint256) {
return type(uint256).max;
}

function mintingCurrentLimitOf(
address _bridge
) external view returns (uint256) {
return type(uint256).max;
}
}

contract XERC20LockboxTest is IXERC20Lockbox {
Expand Down
18 changes: 18 additions & 0 deletions solidity/contracts/token/interfaces/IXERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,22 @@ interface IXERC20 is IERC20 {
) external;

function owner() external returns (address);

/**
* @notice Returns the current limit of a bridge
* @param _bridge the bridge we are viewing the limits of
* @return _limit The limit the bridge has
*/
function burningCurrentLimitOf(
address _bridge
) external view returns (uint256 _limit);

/**
* @notice Returns the current limit of a bridge
* @param _bridge the bridge we are viewing the limits of
* @return _limit The limit the bridge has
*/
function mintingCurrentLimitOf(
address _bridge
) external view returns (uint256 _limit);
}
33 changes: 31 additions & 2 deletions typescript/cli/src/deploy/warp.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { confirm } from '@inquirer/prompts';

import {
HypXERC20Lockbox__factory,
HypXERC20__factory,
} from '@hyperlane-xyz/core';
import {
HypERC20Deployer,
HypERC721Deployer,
Expand Down Expand Up @@ -161,8 +165,33 @@ async function getWarpCoreConfig(
throw new Error('Missing decimals on token metadata');
}

const collateralAddressOrDenom =
config.type === TokenType.collateral ? config.token : undefined;
const collateralAddressOrDenom = await (async () => {
if (config.type === TokenType.XERC20Lockbox) {
const provider = context.multiProvider.tryGetProvider(chainName);
if (!provider) {
throw new Error(`Unable to pull provider for ${chainName}`);
}

const xERC20 = await HypXERC20Lockbox__factory.connect(
config.token,
provider,
).xERC20();
const wrappedToken = await HypXERC20__factory.connect(
xERC20,
provider,
).wrappedToken();
return wrappedToken;
}

if (
config.type === TokenType.collateral ||
config.type === TokenType.XERC20
) {
return config.token;
}

return undefined;
})();
warpCoreConfig.tokens.push({
chainName,
standard: TOKEN_TYPE_TO_STANDARD[config.type],
Expand Down
16 changes: 16 additions & 0 deletions typescript/sdk/src/token/Token.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,22 @@ const STANDARD_TO_TOKEN: Record<TokenStandard, TokenArgs | null> = {
symbol: 'USDC',
name: 'USDC',
},
[TokenStandard.EvmHypXERC20]: {
chainName: TestChainName.test2,
standard: TokenStandard.EvmHypXERC20,
addressOrDenom: '0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147',
decimals: 6,
symbol: 'USDC',
name: 'USDC',
},
[TokenStandard.EvmHypXERC20Lockbox]: {
chainName: TestChainName.test2,
standard: TokenStandard.EvmHypXERC20Lockbox,
addressOrDenom: '0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147',
decimals: 6,
symbol: 'USDC',
name: 'USDC',
},

// Sealevel
[TokenStandard.SealevelSpl]: {
Expand Down
10 changes: 10 additions & 0 deletions typescript/sdk/src/token/Token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import {
EvmHypCollateralAdapter,
EvmHypNativeAdapter,
EvmHypSyntheticAdapter,
EvmHypXERC20Adapter,
EvmHypXERC20LockboxAdapter,
EvmNativeTokenAdapter,
EvmTokenAdapter,
} from './adapters/EvmTokenAdapter.js';
Expand Down Expand Up @@ -213,6 +215,14 @@ export class Token implements IToken {
return new EvmHypSyntheticAdapter(chainName, multiProvider, {
token: addressOrDenom,
});
} else if (standard === TokenStandard.EvmHypXERC20) {
return new EvmHypXERC20Adapter(chainName, multiProvider, {
token: addressOrDenom,
});
} else if (standard === TokenStandard.EvmHypXERC20Lockbox) {
return new EvmHypXERC20LockboxAdapter(chainName, multiProvider, {
token: addressOrDenom,
});
} else if (standard === TokenStandard.SealevelHypNative) {
return new SealevelHypNativeAdapter(
chainName,
Expand Down
12 changes: 10 additions & 2 deletions typescript/sdk/src/token/TokenStandard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export enum TokenStandard {
EvmHypNative = 'EvmHypNative',
EvmHypCollateral = 'EvmHypCollateral',
EvmHypSynthetic = 'EvmHypSynthetic',
EvmHypXERC20 = 'EvmHypXERC20',
EvmHypXERC20Lockbox = 'EvmHypXERC20Lockbox',

// Sealevel (Solana)
SealevelSpl = 'SealevelSpl',
Expand Down Expand Up @@ -48,6 +50,8 @@ export const TOKEN_STANDARD_TO_PROTOCOL: Record<TokenStandard, ProtocolType> = {
EvmHypNative: ProtocolType.Ethereum,
EvmHypCollateral: ProtocolType.Ethereum,
EvmHypSynthetic: ProtocolType.Ethereum,
EvmHypXERC20: ProtocolType.Ethereum,
EvmHypXERC20Lockbox: ProtocolType.Ethereum,

// Sealevel (Solana)
SealevelSpl: ProtocolType.Sealevel,
Expand Down Expand Up @@ -90,6 +94,8 @@ export const TOKEN_NFT_STANDARDS = [
export const TOKEN_COLLATERALIZED_STANDARDS = [
TokenStandard.EvmHypCollateral,
TokenStandard.EvmHypNative,
TokenStandard.EvmHypXERC20,
TokenStandard.EvmHypXERC20Lockbox,
TokenStandard.SealevelHypCollateral,
TokenStandard.SealevelHypNative,
TokenStandard.CwHypCollateral,
Expand All @@ -100,6 +106,8 @@ export const TOKEN_HYP_STANDARDS = [
TokenStandard.EvmHypNative,
TokenStandard.EvmHypCollateral,
TokenStandard.EvmHypSynthetic,
TokenStandard.EvmHypXERC20,
TokenStandard.EvmHypXERC20Lockbox,
TokenStandard.SealevelHypNative,
TokenStandard.SealevelHypCollateral,
TokenStandard.SealevelHypSynthetic,
Expand Down Expand Up @@ -128,8 +136,8 @@ export const TOKEN_TYPE_TO_STANDARD: Record<TokenType, TokenStandard> = {
[TokenType.native]: TokenStandard.EvmHypNative,
[TokenType.collateral]: TokenStandard.EvmHypCollateral,
[TokenType.collateralFiat]: TokenStandard.EvmHypCollateral,
[TokenType.XERC20]: TokenStandard.EvmHypCollateral,
[TokenType.XERC20Lockbox]: TokenStandard.EvmHypCollateral,
[TokenType.XERC20]: TokenStandard.EvmHypXERC20,
[TokenType.XERC20Lockbox]: TokenStandard.EvmHypXERC20Lockbox,
[TokenType.collateralVault]: TokenStandard.EvmHypCollateral,
[TokenType.collateralUri]: TokenStandard.EvmHypCollateral,
[TokenType.fastCollateral]: TokenStandard.EvmHypCollateral,
Expand Down
92 changes: 92 additions & 0 deletions typescript/sdk/src/token/adapters/EvmTokenAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import {
HypERC20Collateral,
HypERC20Collateral__factory,
HypERC20__factory,
HypXERC20,
HypXERC20Lockbox,
HypXERC20Lockbox__factory,
HypXERC20__factory,
IXERC20__factory,
} from '@hyperlane-xyz/core';
import {
Address,
Expand All @@ -25,6 +30,7 @@ import { TokenMetadata } from '../types.js';

import {
IHypTokenAdapter,
IHypXERC20Adapter,
ITokenAdapter,
InterchainGasQuote,
TransferParams,
Expand Down Expand Up @@ -279,6 +285,92 @@ export class EvmHypCollateralAdapter
}
}

// Interacts with HypXERC20Lockbox contracts
export class EvmHypXERC20LockboxAdapter
extends EvmHypCollateralAdapter
implements IHypXERC20Adapter<PopulatedTransaction>
{
hypXERC20Lockbox: HypXERC20Lockbox;

constructor(
public readonly chainName: ChainName,
public readonly multiProvider: MultiProtocolProvider,
public readonly addresses: { token: Address },
) {
super(chainName, multiProvider, addresses);

this.hypXERC20Lockbox = HypXERC20Lockbox__factory.connect(
addresses.token,
this.getProvider(),
);
}

async getMintLimit() {
const xERC20 = await this.hypXERC20Lockbox.xERC20();

const limit = await IXERC20__factory.connect(
xERC20,
this.getProvider(),
).mintingCurrentLimitOf(this.contract.address);

return BigInt(limit.toString());
}

async getBurnLimit() {
const xERC20 = await this.hypXERC20Lockbox.xERC20();

const limit = await IXERC20__factory.connect(
xERC20,
this.getProvider(),
).mintingCurrentLimitOf(this.contract.address);

return BigInt(limit.toString());
}
}

// Interacts with HypXERC20 contracts
export class EvmHypXERC20Adapter
extends EvmHypCollateralAdapter
implements IHypXERC20Adapter<PopulatedTransaction>
{
hypXERC20: HypXERC20;

constructor(
public readonly chainName: ChainName,
public readonly multiProvider: MultiProtocolProvider,
public readonly addresses: { token: Address },
) {
super(chainName, multiProvider, addresses);

this.hypXERC20 = HypXERC20__factory.connect(
addresses.token,
this.getProvider(),
);
}

async getMintLimit() {
const xERC20 = await this.hypXERC20.wrappedToken();

const limit = await IXERC20__factory.connect(
xERC20,
this.getProvider(),
).mintingCurrentLimitOf(this.contract.address);

return BigInt(limit.toString());
}

async getBurnLimit() {
const xERC20 = await this.hypXERC20.wrappedToken();

const limit = await IXERC20__factory.connect(
xERC20,
this.getProvider(),
).burningCurrentLimitOf(this.contract.address);

return BigInt(limit.toString());
}
}

// Interacts HypNative contracts
export class EvmHypNativeAdapter
extends EvmHypCollateralAdapter
Expand Down
5 changes: 5 additions & 0 deletions typescript/sdk/src/token/adapters/ITokenAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,8 @@ export interface IHypTokenAdapter<Tx> extends ITokenAdapter<Tx> {
quoteTransferRemoteGas(destination: Domain): Promise<InterchainGasQuote>;
populateTransferRemoteTx(p: TransferRemoteParams): Promise<Tx>;
}

export interface IHypXERC20Adapter<Tx> extends IHypTokenAdapter<Tx> {
getMintLimit(): Promise<bigint>;
getBurnLimit(): Promise<bigint>;
}
4 changes: 2 additions & 2 deletions typescript/sdk/src/warp/WarpCore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,15 +222,15 @@ describe('WarpCore', () => {

const invalidAmount = await warpCore.validateTransfer({
originTokenAmount: evmHypNative.amount(-10),
destination: test1.name,
destination: test2.name,
recipient: MOCK_ADDRESS,
sender: MOCK_ADDRESS,
});
expect(Object.keys(invalidAmount || {})[0]).to.equal('amount');

const insufficientBalance = await warpCore.validateTransfer({
originTokenAmount: evmHypNative.amount(BIG_TRANSFER_AMOUNT),
destination: test1.name,
destination: test2.name,
recipient: MOCK_ADDRESS,
sender: MOCK_ADDRESS,
});
Expand Down
Loading

0 comments on commit 51bfff6

Please sign in to comment.