-
Notifications
You must be signed in to change notification settings - Fork 255
Add superchain ERC20 example #1433
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
TRileySchwarz
wants to merge
1
commit into
main
Choose a base branch
from
riley/superchain-token
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
62 changes: 62 additions & 0 deletions
62
examples/mint-burn-oft-adapter/contracts/mocks/SuperChainMintBurnERC20.sol
This file contains hidden or 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,62 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.22; | ||
|
|
||
| import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; | ||
| import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
| import { IMintableBurnable } from "@layerzerolabs/oft-evm/contracts/interfaces/IMintableBurnable.sol"; | ||
|
|
||
| interface IERC7802 { | ||
| event CrosschainBurn(address indexed from, uint256 amount, address indexed sender); | ||
| event CrosschainMint(address indexed to, uint256 amount, address indexed sender); | ||
|
|
||
| function crosschainBurn(address _from, uint256 _amount) external; | ||
| function crosschainMint(address _to, uint256 _amount) external; | ||
| } | ||
|
|
||
| contract SuperChainMintBurnERC20 is ERC20, IERC7802, IMintableBurnable, Ownable { | ||
| address public TOKEN_BRIDGE; | ||
|
|
||
| error Unauthorized(); | ||
| error InvalidTokenBridgeAddress(); | ||
|
|
||
| event SetTokenBridge(address _tokenBridge); | ||
|
|
||
| constructor(string memory name, string memory symbol) ERC20(name, symbol) Ownable(msg.sender) {} | ||
|
|
||
| modifier onlyTokenBridge() { | ||
| if (msg.sender != TOKEN_BRIDGE) revert Unauthorized(); | ||
| _; | ||
| } | ||
|
|
||
| function setTokenBridge(address _tokenBridge) external onlyOwner { | ||
| if (_tokenBridge == address(0)) revert InvalidTokenBridgeAddress(); | ||
|
|
||
| TOKEN_BRIDGE = _tokenBridge; | ||
| emit SetTokenBridge(_tokenBridge); | ||
| } | ||
|
|
||
| // @notice 'sender' in these contexts is the caller, i.e. the current tokenBridge, | ||
| // It is NOT the 'sender' from the src chain who initialized the transfer | ||
|
|
||
| // Functions to handle IERC7802.sol | ||
| function crosschainBurn(address _from, uint256 _amount) external onlyTokenBridge { | ||
| _burn(_from, _amount); | ||
| emit CrosschainBurn(_from, _amount, msg.sender); | ||
| } | ||
| function crosschainMint(address _to, uint256 _amount) external onlyTokenBridge { | ||
| _mint(_to, _amount); | ||
| emit CrosschainMint(_to, _amount, msg.sender); | ||
| } | ||
|
|
||
| // Functions to handle MintBurnOFTAdapter.sol | ||
| function burn(address _from, uint256 _amount) external onlyTokenBridge returns (bool) { | ||
| _burn(_from, _amount); | ||
| emit CrosschainBurn(_from, _amount, msg.sender); | ||
| return true; | ||
| } | ||
| function mint(address _to, uint256 _amount) external onlyTokenBridge returns (bool) { | ||
| _mint(_to, _amount); | ||
| emit CrosschainMint(_to, _amount, msg.sender); | ||
| return true; | ||
| } | ||
| } | ||
123 changes: 123 additions & 0 deletions
123
examples/mint-burn-oft-adapter/test/hardhat/MySuperChainMintBurnOFTAdapter.test.ts
This file contains hidden or 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,123 @@ | ||
| import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' | ||
| import { expect } from 'chai' | ||
| import { Contract, ContractFactory } from 'ethers' | ||
| import { deployments, ethers } from 'hardhat' | ||
|
|
||
| import { Options } from '@layerzerolabs/lz-v2-utilities' | ||
|
|
||
| describe('MySuperChainMintBurnOFTAdapter Test', function () { | ||
| // Constant representing a mock Endpoint ID for testing purposes | ||
| const eidA = 1 | ||
| const eidB = 2 | ||
| // Declaration of variables to be used in the test suite | ||
| let MyMintBurnOFTAdapter: ContractFactory | ||
| let MyOFT: ContractFactory | ||
| let MintBurnERC20Mock: ContractFactory | ||
| let EndpointV2Mock: ContractFactory | ||
| let ownerA: SignerWithAddress | ||
| let ownerB: SignerWithAddress | ||
| let endpointOwner: SignerWithAddress | ||
| let token: Contract | ||
| let myMintBurnOFTAdapterA: Contract | ||
| let myOFTB: Contract | ||
| let mockEndpointV2A: Contract | ||
| let mockEndpointV2B: Contract | ||
|
|
||
| // Before hook for setup that runs once before all tests in the block | ||
| before(async function () { | ||
| // Contract factory for our tested contract | ||
| // | ||
| // We are using a derived contract that exposes a mint() function for testing purposes | ||
| MyMintBurnOFTAdapter = await ethers.getContractFactory('MyMintBurnOFTAdapterMock') | ||
|
|
||
| MyOFT = await ethers.getContractFactory('MyOFTMock') | ||
|
|
||
| MintBurnERC20Mock = await ethers.getContractFactory('SuperChainMintBurnERC20') | ||
|
|
||
| // Fetching the first three signers (accounts) from Hardhat's local Ethereum network | ||
| const signers = await ethers.getSigners() | ||
|
|
||
| ;[ownerA, ownerB, endpointOwner] = signers | ||
|
|
||
| // The EndpointV2Mock contract comes from @layerzerolabs/test-devtools-evm-hardhat package | ||
| // and its artifacts are connected as external artifacts to this project | ||
| // | ||
| // Unfortunately, hardhat itself does not yet provide a way of connecting external artifacts, | ||
| // so we rely on hardhat-deploy to create a ContractFactory for EndpointV2Mock | ||
| // | ||
| // See https://github.com/NomicFoundation/hardhat/issues/1040 | ||
| const EndpointV2MockArtifact = await deployments.getArtifact('EndpointV2Mock') | ||
| EndpointV2Mock = new ContractFactory(EndpointV2MockArtifact.abi, EndpointV2MockArtifact.bytecode, endpointOwner) | ||
| }) | ||
|
|
||
| // beforeEach hook for setup that runs before each test in the block | ||
| beforeEach(async function () { | ||
| // Deploying a mock LZEndpoint with the given Endpoint ID | ||
| mockEndpointV2A = await EndpointV2Mock.deploy(eidA) | ||
| mockEndpointV2B = await EndpointV2Mock.deploy(eidB) | ||
|
|
||
| token = await MintBurnERC20Mock.deploy('Token', 'TOKEN') | ||
|
|
||
| // Deploying two instances of MyOFT contract with different identifiers and linking them to the mock LZEndpoint | ||
| myMintBurnOFTAdapterA = await MyMintBurnOFTAdapter.deploy( | ||
| token.address, | ||
| token.address, | ||
| mockEndpointV2A.address, | ||
| ownerA.address | ||
| ) | ||
| // Set the token bridge to be the adapter | ||
| await token.setTokenBridge(myMintBurnOFTAdapterA.address) | ||
| myOFTB = await MyOFT.deploy('bOFT', 'bOFT', mockEndpointV2B.address, ownerB.address) | ||
|
|
||
| // Setting destination endpoints in the LZEndpoint mock for each MyOFT instance | ||
| await mockEndpointV2A.setDestLzEndpoint(myOFTB.address, mockEndpointV2B.address) | ||
| await mockEndpointV2B.setDestLzEndpoint(myMintBurnOFTAdapterA.address, mockEndpointV2A.address) | ||
|
|
||
| // Setting each MyOFT instance as a peer of the other in the mock LZEndpoint | ||
| await myMintBurnOFTAdapterA.connect(ownerA).setPeer(eidB, ethers.utils.zeroPad(myOFTB.address, 32)) | ||
| await myOFTB.connect(ownerB).setPeer(eidA, ethers.utils.zeroPad(myMintBurnOFTAdapterA.address, 32)) | ||
| }) | ||
|
|
||
| // A test case to verify token transfer functionality | ||
| it('should send a token from A address to B address via OFTAdapter/OFT', async function () { | ||
| // Minting an initial amount of tokens to ownerA's address in the myOFTA contract | ||
| const initialAmount = ethers.utils.parseEther('100') | ||
|
|
||
| // Temporarily setting the token bridge to the ownerA address for minting | ||
| await token.setTokenBridge(ownerA.address) | ||
| await token.mint(ownerA.address, initialAmount) | ||
| await token.setTokenBridge(myMintBurnOFTAdapterA.address) | ||
|
|
||
| // Defining the amount of tokens to send and constructing the parameters for the send operation | ||
| const tokensToSend = ethers.utils.parseEther('1') | ||
|
|
||
| // Defining extra message execution options for the send operation | ||
| const options = Options.newOptions().addExecutorLzReceiveOption(200000, 0).toHex().toString() | ||
|
|
||
| const sendParam = [ | ||
| eidB, | ||
| ethers.utils.zeroPad(ownerB.address, 32), | ||
| tokensToSend, | ||
| tokensToSend, | ||
| options, | ||
| '0x', | ||
| '0x', | ||
| ] | ||
|
|
||
| // Fetching the native fee for the token send operation | ||
| const [nativeFee] = await myMintBurnOFTAdapterA.quoteSend(sendParam, false) | ||
|
|
||
| // Executing the send operation from myOFTA contract | ||
| await myMintBurnOFTAdapterA.send(sendParam, [nativeFee, 0], ownerA.address, { value: nativeFee }) | ||
|
|
||
| // Fetching the final token balances of ownerA and ownerB | ||
| const finalBalanceA = await token.balanceOf(ownerA.address) | ||
| const finalBalanceAdapter = await token.balanceOf(myMintBurnOFTAdapterA.address) | ||
| const finalBalanceB = await myOFTB.balanceOf(ownerB.address) | ||
|
|
||
| // Asserting that the final balances are as expected after the send operation | ||
| expect(finalBalanceA).eql(initialAmount.sub(tokensToSend)) | ||
| expect(finalBalanceAdapter).eql(ethers.utils.parseEther('0')) | ||
| expect(finalBalanceB).eql(tokensToSend) | ||
| }) | ||
| }) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this in
examplesinstead ofoft-evm? Shouldn't it be inoft-evmso folks don't need to copy + paste to use it?