-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
278 additions
and
34 deletions.
There are no files selected for viewing
This file contains 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 |
---|---|---|
|
@@ -12,3 +12,6 @@ docs/ | |
|
||
# Dotenv file | ||
.env | ||
|
||
# IDE files | ||
.vscode/ |
This file contains 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 |
---|---|---|
@@ -1,6 +1,6 @@ | ||
[submodule "hello_foundry/lib/forge-std"] | ||
path = hello_foundry/lib/forge-std | ||
url = https://github.com/foundry-rs/forge-std | ||
[submodule "lib/contracts"] | ||
path = lib/contracts | ||
url = https://github.com/Analog-Labs/contracts | ||
[submodule "lib/analog-gmp"] | ||
path = lib/analog-gmp | ||
url = https://github.com/Analog-Labs/analog-gmp |
This file contains 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
This file contains 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
This file contains 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,96 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity >=0.8.0; | ||
|
||
import {ERC20} from "@solmate/tokens/ERC20.sol"; | ||
import {IGmpReceiver} from "@analog-gmp/interfaces/IGmpReceiver.sol"; | ||
import {IGateway} from "@analog-gmp/interfaces/IGateway.sol"; | ||
import {GmpSender, PrimitiveUtils} from "@analog-gmp/Primitives.sol"; | ||
|
||
contract BasicERC20 is ERC20, IGmpReceiver { | ||
using PrimitiveUtils for GmpSender; | ||
|
||
IGateway private immutable _trustedGateway; | ||
BasicERC20 private immutable _recipientErc20; | ||
uint16 private immutable _recipientNetwork; | ||
|
||
/** | ||
* @dev Emitted when `amount` tokens are teleported from one account (`from`) in this chain to | ||
* another (`to`) in another chain. | ||
* | ||
* Note Is not necessary to emit the destination network, because this is already emitted by the gateway in `GmpCreated` event. | ||
*/ | ||
event OutboundTransfer(bytes32 indexed id, address indexed from, address indexed to, uint256 amount); | ||
|
||
/** | ||
* @dev @dev Emitted when `amount` tokens are teleported from one account (`from`) in another chain to | ||
* an account (`to`) in this chain. | ||
* | ||
* Note Is not necessary to emit the source network, because this is already emitted by the gateway in `GmpExecuted` event. | ||
*/ | ||
event InboundTransfer(bytes32 indexed id, address indexed from, address indexed to, uint256 amount); | ||
|
||
/** | ||
* @dev Gas limit used to execute `onGmpReceived` method. | ||
*/ | ||
uint256 private constant MSG_GAS_LIMIT = 100_000; | ||
|
||
/** | ||
* @dev Command that will be encoded in the `data` field on the `onGmpReceived` method. | ||
*/ | ||
struct TeleportCommand { | ||
address from; | ||
address to; | ||
uint256 amount; | ||
} | ||
|
||
constructor( | ||
string memory name, | ||
string memory symbol, | ||
IGateway gatewayAddress, | ||
BasicERC20 recipient, | ||
uint16 recipientNetwork, | ||
address holder, | ||
uint256 initialSupply | ||
) ERC20(name, symbol, 10) { | ||
_trustedGateway = gatewayAddress; | ||
_recipientErc20 = recipient; | ||
_recipientNetwork = recipientNetwork; | ||
if (initialSupply > 0) { | ||
_mint(holder, initialSupply); | ||
} | ||
} | ||
|
||
/** | ||
* @dev Teleport tokens from `msg.sender` to `recipient` in `_recipientNetwork` | ||
*/ | ||
function teleport(address recipient, uint256 amount) external returns (bytes32 messageID) { | ||
_burn(msg.sender, amount); | ||
bytes memory message = abi.encode(TeleportCommand({from: msg.sender, to: recipient, amount: amount})); | ||
messageID = _trustedGateway.submitMessage(address(_recipientErc20), _recipientNetwork, MSG_GAS_LIMIT, message); | ||
emit OutboundTransfer(messageID, msg.sender, recipient, amount); | ||
} | ||
|
||
function onGmpReceived(bytes32 id, uint128 network, bytes32 sender, bytes calldata data) | ||
external | ||
payable | ||
returns (bytes32) | ||
{ | ||
// Convert bytes32 to address | ||
address senderAddr = GmpSender.wrap(sender).toAddress(); | ||
|
||
// Validate the message | ||
require(msg.sender == address(_trustedGateway), "Unauthorized: only the gateway can call this method"); | ||
require(network == _recipientNetwork, "Unauthorized network"); | ||
require(senderAddr == address(_recipientErc20), "Unauthorized sender"); | ||
|
||
// Decode the command | ||
TeleportCommand memory command = abi.decode(data, (TeleportCommand)); | ||
|
||
// Mint the tokens to the destination account | ||
_mint(command.to, command.amount); | ||
emit InboundTransfer(id, command.from, command.to, command.amount); | ||
|
||
return id; | ||
} | ||
} |
This file contains 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 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity >=0.8.0; | ||
|
||
import {Test} from "forge-std/Test.sol"; | ||
import {BasicERC20} from "./BasicERC20.sol"; | ||
import {GmpTestTools} from "@analog-gmp-testing/GmpTestTools.sol"; | ||
import {Gateway} from "@analog-gmp/Gateway.sol"; | ||
import {IGateway} from "@analog-gmp/interfaces/IGateway.sol"; | ||
import {GmpMessage, GmpStatus, GmpSender, PrimitiveUtils} from "@analog-gmp/Primitives.sol"; | ||
|
||
contract GmpTestToolsTest is Test { | ||
using PrimitiveUtils for GmpSender; | ||
using PrimitiveUtils for address; | ||
|
||
address private constant ALICE = address(bytes20(keccak256("Alice"))); | ||
address private constant BOB = address(bytes20(keccak256("Bob"))); | ||
|
||
Gateway private constant SEPOLIA_GATEWAY = Gateway(GmpTestTools.SEPOLIA_GATEWAY); | ||
uint16 private constant SEPOLIA_NETWORK = GmpTestTools.SEPOLIA_NETWORK_ID; | ||
|
||
Gateway private constant SHIBUYA_GATEWAY = Gateway(GmpTestTools.SHIBUYA_GATEWAY); | ||
uint16 private constant SHIBUYA_NETWORK = GmpTestTools.SHIBUYA_NETWORK_ID; | ||
|
||
/// @dev Test the teleport of tokens from Alice's account in Shibuya to Bob's account in Sepolia | ||
function test_teleportTokens() external { | ||
//////////////////////////////////// | ||
// Step 1: Setup test environment // | ||
//////////////////////////////////// | ||
|
||
// Deploy the gateway contracts at pre-defined addresses | ||
// Also creates one fork for each supported network | ||
GmpTestTools.setup(); | ||
|
||
// Add funds to Alice and Bob in all networks | ||
GmpTestTools.deal(ALICE, 100 ether); | ||
GmpTestTools.deal(BOB, 100 ether); | ||
|
||
/////////////////////////////////////////////////////// | ||
// Step 2: Deploy the sender and recipient contracts // | ||
/////////////////////////////////////////////////////// | ||
|
||
// Pre-compute the contract addresses, because the contracts must know each other addresses. | ||
BasicERC20 shibuyaErc20 = BasicERC20(vm.computeCreateAddress(ALICE, vm.getNonce(ALICE))); | ||
BasicERC20 sepoliaErc20 = BasicERC20(vm.computeCreateAddress(BOB, vm.getNonce(BOB))); | ||
|
||
// Switch to Shibuya network and deploy the ERC20 using Alice account | ||
GmpTestTools.switchNetwork(SHIBUYA_NETWORK, ALICE); | ||
shibuyaErc20 = new BasicERC20("Shibuya ", "A", SHIBUYA_GATEWAY, sepoliaErc20, SEPOLIA_NETWORK, ALICE, 1000); | ||
assertEq(shibuyaErc20.balanceOf(ALICE), 1000, "unexpected alice balance in shibuya"); | ||
assertEq(shibuyaErc20.balanceOf(BOB), 0, "unexpected bob balance in shibuya"); | ||
|
||
// Switch to Sepolia network and deploy the ERC20 using Bob account | ||
GmpTestTools.switchNetwork(SEPOLIA_NETWORK, BOB); | ||
sepoliaErc20 = new BasicERC20("Sepolia", "B", SEPOLIA_GATEWAY, shibuyaErc20, SHIBUYA_NETWORK, BOB, 0); | ||
assertEq(sepoliaErc20.balanceOf(ALICE), 0, "unexpected alice balance in sepolia"); | ||
assertEq(sepoliaErc20.balanceOf(BOB), 0, "unexpected bob balance in sepolia"); | ||
|
||
// Check if the computed addresses matches | ||
assertEq(address(shibuyaErc20), vm.computeCreateAddress(ALICE, 0), "unexpected shibuyaErc20 address"); | ||
assertEq(address(sepoliaErc20), vm.computeCreateAddress(BOB, 0), "unexpected sepoliaErc20 address"); | ||
|
||
/////////////////////////////////////////////////////////// | ||
// Step 3: Deposit funds to destination Gateway Contract // | ||
/////////////////////////////////////////////////////////// | ||
|
||
// Switch to Sepolia network and Alice account | ||
GmpTestTools.switchNetwork(SEPOLIA_NETWORK, ALICE); | ||
// If the sender is a contract, it's address must be converted | ||
GmpSender sender = address(shibuyaErc20).toSender(true); | ||
// Alice deposit 1 ether to Sepolia gateway contract | ||
SEPOLIA_GATEWAY.deposit{value: 1 ether}(sender, SHIBUYA_NETWORK); | ||
|
||
////////////////////////////// | ||
// Step 4: Send GMP message // | ||
////////////////////////////// | ||
|
||
// Switch to Shibuya network and Alice account | ||
GmpTestTools.switchNetwork(SHIBUYA_NETWORK, ALICE); | ||
|
||
// Teleport 100 tokens from Alice to to Bob's account in sepolia | ||
// Obs: The `teleport` method internally calls `gateway.submitMessage(...)` | ||
vm.expectEmit(false, true, false, true, address(shibuyaErc20)); | ||
emit BasicERC20.OutboundTransfer(bytes32(0), ALICE, BOB, 100); | ||
bytes32 messageID = shibuyaErc20.teleport(BOB, 100); | ||
|
||
// Now with the `messageID`, Alice can check the message status in the destination gateway contract | ||
// status 0: means the message is pending | ||
// status 1: means the message was executed successfully | ||
// status 2: means the message was executed but reverted | ||
GmpTestTools.switchNetwork(SEPOLIA_NETWORK, ALICE); | ||
assertTrue( | ||
SEPOLIA_GATEWAY.gmpInfo(messageID).status == GmpStatus.NOT_FOUND, | ||
"unexpected message status, expect 'pending'" | ||
); | ||
|
||
/////////////////////////////////////////////////// | ||
// Step 5: Wait Chronicles Relay the GMP message // | ||
/////////////////////////////////////////////////// | ||
|
||
// The GMP hasn't been executed yet... | ||
assertEq(sepoliaErc20.balanceOf(ALICE), 0, "unexpected alice balance in shibuya"); | ||
|
||
// Note: In a live network, the GMP message will be relayed by Chronicle Nodes after a minimum number of confirmations. | ||
// here we can simulate this behavior by calling `GmpTestTools.relayMessages()`, this will relay all pending messages. | ||
vm.expectEmit(true, true, false, true, address(sepoliaErc20)); | ||
emit BasicERC20.InboundTransfer(messageID, ALICE, BOB, 100); | ||
GmpTestTools.relayMessages(); | ||
|
||
// Success! The GMP message was executed!!! | ||
assertTrue(SEPOLIA_GATEWAY.gmpInfo(messageID).status == GmpStatus.SUCCESS, "failed to execute GMP"); | ||
|
||
// Check ALICE and BOB balance in shibuya | ||
GmpTestTools.switchNetwork(SHIBUYA_NETWORK); | ||
assertEq(shibuyaErc20.balanceOf(ALICE), 900, "unexpected alice's balance in shibuya"); | ||
assertEq(shibuyaErc20.balanceOf(BOB), 0, "unexpected bob's balance in shibuya"); | ||
|
||
// Check ALICE and BOB balance in sepolia | ||
GmpTestTools.switchNetwork(SEPOLIA_NETWORK); | ||
assertEq(sepoliaErc20.balanceOf(ALICE), 0, "unexpected alice's balance in sepolia"); | ||
assertEq(sepoliaErc20.balanceOf(BOB), 100, "unexpected bob's balance in sepolia"); | ||
} | ||
} |
This file contains 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,7 @@ | ||
# Teleport Tokens | ||
|
||
This example demonstrates how teleport an ERC20 from Alice's account in Shibuya to Bob's account in Sepolia. | ||
|
||
## License | ||
|
||
Analog's Examples is released under the [MIT License](../../LICENSE). |
Submodule analog-gmp
added at
42a722
Submodule contracts
deleted from
edacd4
This file was deleted.
Oops, something went wrong.