Skip to content

Commit

Permalink
WIP multichain
Browse files Browse the repository at this point in the history
  • Loading branch information
dmax10 committed Jan 28, 2025
1 parent 233f187 commit 70dba6c
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 49 deletions.
8 changes: 5 additions & 3 deletions contracts/data/Keys.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2098,13 +2098,15 @@ library Keys {
}

// @dev key for user's balance for a source chain, recorded under the user's virtual account
// @param virtualAccount the virtual account for which to retreive the user balance key
// @param chianId the chain id for the source chain
// @param account the account for which to retreive the user balance key
// @param token the token for which to retreive the user balance key
// @return key for a source chain balance for a given user and token
function sourceChainBalanceKey(address virtualAccount, address token) internal pure returns (bytes32) {
function sourceChainBalanceKey(uint256 chainId, address account, address token) internal pure returns (bytes32) {
return keccak256(abi.encode(
SOURCE_CHAIN_BALANCE,
virtualAccount,
chainId,
account,
token
));
}
Expand Down
5 changes: 3 additions & 2 deletions contracts/multichain/LayerZeroProvider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,12 @@ contract LayerZeroProvider is IMultichainProvider, ILayerZeroComposer {

_transferToVault(token, address(multichainVault));

address virtualAccount = multichainHandler.recordDeposit(account, token, sourceChainId);
multichainHandler.recordDeposit(account, token, sourceChainId);

LayerZeroProviderEventUtils.emitComposedMessageReceived(
eventEmitter,
virtualAccount,
sourceChainId,
account,
from,
guid,
message,
Expand Down
18 changes: 12 additions & 6 deletions contracts/multichain/LayerZeroProviderEventUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ library LayerZeroProviderEventUtils {

function emitComposedMessageReceived(
EventEmitter eventEmitter,
address virtualAccount,
uint256 sourceChainId,
address account,
address from,
bytes32 guid,
bytes calldata message,
Expand All @@ -27,9 +28,12 @@ library LayerZeroProviderEventUtils {
EventUtils.EventLogData memory eventData;

eventData.addressItems.initItems(3);
eventData.addressItems.setItem(0, "virtualAccount", virtualAccount);
eventData.addressItems.setItem(0, "account", account);
eventData.addressItems.setItem(1, "from", from);
eventData.addressItems.setItem(2, "executor", executor);

eventData.uintItems.initItems(1);
eventData.uintItems.setItem(0, "sourceChainId", sourceChainId);

eventData.bytes32Items.initItems(1);
eventData.bytes32Items.setItem(0, "guid", guid);
Expand All @@ -38,12 +42,13 @@ library LayerZeroProviderEventUtils {
eventData.bytesItems.setItem(0, "message", message);
eventData.bytesItems.setItem(1, "extraData", extraData);

eventEmitter.emitEventLog1("MessageComposedReceived", Cast.toBytes32(virtualAccount), eventData);
eventEmitter.emitEventLog2("MessageComposedReceived", bytes32(sourceChainId), Cast.toBytes32(account), eventData);
}

function emitWithdrawalReceipt(
EventEmitter eventEmitter,
address virtualAccount,
uint256 sourceChainId,
address account,
bytes32 guid,
uint64 nonce,
uint256 nativeFee,
Expand All @@ -53,16 +58,17 @@ library LayerZeroProviderEventUtils {
) internal {
EventUtils.EventLogData memory eventData;

eventData.uintItems.initItems(5);
eventData.uintItems.initItems(6);
eventData.uintItems.setItem(0, "nonce", uint256(nonce));
eventData.uintItems.setItem(1, "nativeFee", nativeFee);
eventData.uintItems.setItem(2, "lzTokenFee", lzTokenFee);
eventData.uintItems.setItem(3, "amountSentLD", amountSentLD);
eventData.uintItems.setItem(4, "amountReceivedLD", amountReceivedLD);
eventData.uintItems.setItem(5, "sourceChainId", sourceChainId);

eventData.bytes32Items.initItems(1);
eventData.bytes32Items.setItem(0, "guid", guid);

eventEmitter.emitEventLog1("WithdrawalReceipt", Cast.toBytes32(virtualAccount), eventData);
eventEmitter.emitEventLog2("WithdrawalReceipt", bytes32(sourceChainId), Cast.toBytes32(account), eventData);
}
}
18 changes: 9 additions & 9 deletions contracts/multichain/MultichainEventUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,56 +16,56 @@ library MultichainEventUtils {
function emitMultichainDeposit(
EventEmitter eventEmitter,
address token,
address virtualAccount,
address account,
uint256 amount,
uint256 sourceChainId
) internal {
EventUtils.EventLogData memory eventData;

eventData.addressItems.initItems(2);
eventData.addressItems.setItem(0, "token", token);
eventData.addressItems.setItem(1, "virtualAccount", virtualAccount);
eventData.addressItems.setItem(1, "account", account);

eventData.uintItems.initItems(2);
eventData.uintItems.setItem(0, "amount", amount);
eventData.uintItems.setItem(1, "sourceChainId", sourceChainId);

eventEmitter.emitEventLog1("MultichainDeposit", Cast.toBytes32(virtualAccount), eventData);
eventEmitter.emitEventLog2("MultichainDeposit", bytes32(sourceChainId), Cast.toBytes32(account), eventData);
}

function emitMultichainMessage(
EventEmitter eventEmitter,
address virtualAccount,
address account,
uint256 sourceChainId
) internal {
EventUtils.EventLogData memory eventData;

eventData.addressItems.initItems(1);
eventData.addressItems.setItem(0, "virtualAccount", virtualAccount);
eventData.addressItems.setItem(0, "account", account);

eventData.uintItems.initItems(1);
eventData.uintItems.setItem(0, "sourceChainId", sourceChainId);

eventEmitter.emitEventLog1("MultichainMessage", Cast.toBytes32(virtualAccount), eventData);
eventEmitter.emitEventLog1("MultichainMessage", Cast.toBytes32(account), eventData);
}

function emitMultichainWithdrawal(
EventEmitter eventEmitter,
address token,
address virtualAccount,
address account,
uint256 amount,
uint256 sourceChainId
) internal {
EventUtils.EventLogData memory eventData;

eventData.addressItems.initItems(2);
eventData.addressItems.setItem(0, "token", token);
eventData.addressItems.setItem(1, "virtualAccount", virtualAccount);
eventData.addressItems.setItem(1, "account", account);

eventData.uintItems.initItems(2);
eventData.uintItems.setItem(0, "amount", amount);
eventData.uintItems.setItem(1, "sourceChainId", sourceChainId);

eventEmitter.emitEventLog1("MultichainWithdrawal", Cast.toBytes32(virtualAccount), eventData);
eventEmitter.emitEventLog2("MultichainWithdrawal", bytes32(sourceChainId), Cast.toBytes32(account), eventData);
}
}
39 changes: 28 additions & 11 deletions contracts/multichain/MultichainHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import { RoleStore } from "../role/RoleStore.sol";
import { RoleModule } from "../role/RoleModule.sol";
import { DataStore } from "../data/DataStore.sol";
Expand All @@ -21,6 +24,8 @@ import { MultichainEventUtils } from "./MultichainEventUtils.sol";
* @title MultichainHandler
*/
contract MultichainHandler is RoleModule, GlobalReentrancyGuard, OracleModule {
using SafeERC20 for IERC20;

MultichainVault public multichainVault;
EventEmitter public eventEmitter;
ExchangeRouter public exchangeRouter;
Expand All @@ -39,7 +44,7 @@ contract MultichainHandler is RoleModule, GlobalReentrancyGuard, OracleModule {
}

/**
* Records a deposit from another chain. IMultichainProvider has MULTICHAIN_CONTROLLER role
* Records a deposit from another chain. IMultichainProvider has CONTROLLER role
* @param account user address on the source chain
* @param token address of the token being deposited
* @param sourceChainId chain id of the source chain
Expand All @@ -48,18 +53,32 @@ contract MultichainHandler is RoleModule, GlobalReentrancyGuard, OracleModule {
address account,
address token,
uint256 sourceChainId
) external onlyController returns (address virtualAccount) {
) external onlyController {
// token should have been transferred to multichainVault by IMultichainProvider
uint256 amount = multichainVault.recordTransferIn(token);
if (amount == 0) {
revert Errors.EmptyMultichainDepositAmount();
}

virtualAccount = MultichainUtils.getVirtualAccount(account, sourceChainId);
dataStore.incrementUint(Keys.sourceChainBalanceKey(sourceChainId, account, token), amount);

MultichainEventUtils.emitMultichainDeposit(eventEmitter, token, account, amount, sourceChainId);
}

/**
* @dev transfer the specified amount of tokens from multichain vault to receiver
* @param sourceChainId the id of the source chain
* @param account the account for which the tokens are subtracted
* @param token the token to transfer
* @param receiver the account to transfer to
* @param amount the amount of tokens to transfer
*/
function pluginTransfer(address account, address token, address receiver, uint256 amount, uint256 sourceChainId) external onlyRouterPlugin { // TODO: confirm access control
dataStore.decrementUint(Keys.sourceChainBalanceKey(sourceChainId, account, token), amount);

dataStore.incrementUint(Keys.sourceChainBalanceKey(virtualAccount, token), amount);
IERC20(token).safeTransferFrom(address(multichainVault), receiver, amount);

MultichainEventUtils.emitMultichainDeposit(eventEmitter, token, virtualAccount, amount, sourceChainId);
MultichainEventUtils.emitMultichainWithdrawal(eventEmitter, token, receiver, amount, sourceChainId);
}

/**
Expand All @@ -77,12 +96,11 @@ contract MultichainHandler is RoleModule, GlobalReentrancyGuard, OracleModule {
// execute multicall
exchangeRouter.multicall(multicallArgs);

address virtualAccount = MultichainUtils.getVirtualAccount(account, sourceChainId);
MultichainEventUtils.emitMultichainMessage(eventEmitter, virtualAccount, sourceChainId);
MultichainEventUtils.emitMultichainMessage(eventEmitter, account, sourceChainId);
}

/**
* Record a withdrawal to another chain. IMultichainProvider has MULTICHAIN_CONTROLLER role
* Record a withdrawal to another chain. IMultichainProvider has CONTROLLER role
* @param account user address on the source chain
* @param token address of the token being withdrawn
* @param amount amount of token being withdrawn
Expand All @@ -98,8 +116,7 @@ contract MultichainHandler is RoleModule, GlobalReentrancyGuard, OracleModule {
revert Errors.EmptyMultichainWithdrawalAmount();
}

address virtualAccount = MultichainUtils.getVirtualAccount(account, sourceChainId);
bytes32 balanceKey = Keys.sourceChainBalanceKey(virtualAccount, token);
bytes32 balanceKey = Keys.sourceChainBalanceKey(sourceChainId, account, token);

uint256 balance = dataStore.getUint(balanceKey);
if (balance < amount) {
Expand All @@ -112,6 +129,6 @@ contract MultichainHandler is RoleModule, GlobalReentrancyGuard, OracleModule {
// transfer tokens to IMultichainProvider
multichainVault.transferOut(token, msg.sender, amount);

MultichainEventUtils.emitMultichainWithdrawal(eventEmitter, token, virtualAccount, amount, sourceChainId);
MultichainEventUtils.emitMultichainWithdrawal(eventEmitter, token, account, amount, sourceChainId);
}
}
28 changes: 28 additions & 0 deletions contracts/multichain/MultichainRouter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.0;

import "../router/relay/GelatoRelayRouter.sol";
import "./MultichainHandler.sol";

contract MultichainRouter is GelatoRelayRouter {
MultichainHandler multichainHandler;

constructor(
Router _router,
DataStore _dataStore,
EventEmitter _eventEmitter,
Oracle _oracle,
IOrderHandler _orderHandler,
OrderVault _orderVault,
MultichainHandler _multichainHandler
) GelatoRelayRouter(_router, _dataStore, _eventEmitter, _oracle, _orderHandler, _orderVault) {
multichainHandler = _multichainHandler;
}

function _sendTokens(address account, address token, address receiver, uint256 amount) internal override { // TODO: override BaseGelatoRelayRouter._sendTokens?
uint256 sourceChainId; // TODO
AccountUtils.validateReceiver(receiver);
multichainHandler.pluginTransfer(account, token, receiver, amount, sourceChainId);
}
}
6 changes: 1 addition & 5 deletions contracts/multichain/MultichainUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,4 @@ pragma solidity ^0.8.0;
/**
* @title MultichainUtils
*/
library MultichainUtils {
function getVirtualAccount(address account, uint256 sourceChainId) internal pure returns (address) {
return address(uint160(uint256(keccak256(abi.encode("GMX Multichain", account, sourceChainId)))));
}
}
library MultichainUtils {}
5 changes: 2 additions & 3 deletions test/multichain/LayerZeroProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,9 @@ describe("LayerZeroProvider", () => {

const lzUsdcBalance = await usdc.balanceOf(layerZeroProvider.address);
const multichainVaultBalance = await usdc.balanceOf(multichainVault.address);
const virtualAccount = multichain.getVirtualAccount(user0.address, sourceChainId);
const userBalance = await dataStore.getUint(keys.sourceChainBalanceKey(virtualAccount, usdc.address));
const userBalance = await dataStore.getUint(keys.sourceChainBalanceKey(sourceChainId, user0.address, usdc.address));

// usdc has been transterred from LayerZeroProvider to MultichainVault and recorded under the user's virtual account
// usdc has been transterred from LayerZeroProvider to MultichainVault and recorded under the user's chainId + account
expect(lzUsdcBalance).eq(0);
expect(multichainVaultBalance).eq(amountUsdc);
expect(userBalance).eq(amountUsdc);
Expand Down
4 changes: 2 additions & 2 deletions utils/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,6 @@ export function withdrawableBuybackTokenAmountKey(buybackToken: string) {
return hashData(["bytes32", "address"], [WITHDRAWABLE_BUYBACK_TOKEN_AMOUNT, buybackToken]);
}

export function sourceChainBalanceKey(virtualAccount: string, token: string) {
return hashData(["bytes32", "address", "address"], [SOURCE_CHAIN_BALANCE, virtualAccount, token]);
export function sourceChainBalanceKey(sourceChainId: number, account: string, token: string) {
return hashData(["bytes32", "uint256", "address", "address"], [SOURCE_CHAIN_BALANCE, sourceChainId, account, token]);
}
8 changes: 0 additions & 8 deletions utils/multichain.ts
Original file line number Diff line number Diff line change
@@ -1,8 +0,0 @@
import { hashData, getAddressFromHash } from "./hash";

export const GMX_MULTICHAIN = "GMX Multichain";

export function getVirtualAccount(account: string, sourceChainId: number): string {
const hash = hashData(["string", "address", "uint256"], [GMX_MULTICHAIN, account, sourceChainId]);
return getAddressFromHash(hash);
}

0 comments on commit 70dba6c

Please sign in to comment.