Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/vectorized/solady
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts
Submodule openzeppelin-contracts added at e4f702
10 changes: 5 additions & 5 deletions src/across/AcrossV3Helper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ import "forge-std/Test.sol";
import {IAcrossSpokePoolV3} from "./interfaces/IAcrossSpokePoolV3.sol";
import {IERC20} from "./interfaces/IERC20.sol";


/// @title AcrossV3 Helper
/// @notice helps simulate AcrossV3 message relaying
contract AcrossV3Helper is Test {
bytes32 constant V3FundsDeposited = keccak256(
"V3FundsDeposited(address,address,uint256,uint256,uint256,uint32,uint32,uint32,uint32,address,address,address,bytes)"
);

bytes32 constant FundsDeposited = keccak256(
"FundsDeposited(bytes32,bytes32,uint256,uint256,uint256,uint256,uint32,uint32,uint32,bytes32,bytes32,bytes32,bytes)"
);
Expand Down Expand Up @@ -136,11 +135,12 @@ contract AcrossV3Helper is Test {
// V3FundsDeposited is the event selector for the V3FundsDeposited event emitted by the SpokePool contract
// Relayers should note that all deposits in V3 are associated with V3FundsDeposited events
// and must be filled using the fillV3Relay function of the SpokePool contract.
if ( (args.logs[i].topics[0] == FundsDeposited || args.logs[i].topics[0] == V3FundsDeposited) && args.logs[i].emitter == args.sourceSpokePool) {
if (
(args.logs[i].topics[0] == FundsDeposited || args.logs[i].topics[0] == V3FundsDeposited)
&& args.logs[i].emitter == args.sourceSpokePool
) {
vars.destinationChainId = uint256(args.logs[i].topics[1]);



if (vars.destinationChainId == args.destinationChainId) {
vars.logData = _decodeLogData(args.logs[i]);

Expand Down
119 changes: 43 additions & 76 deletions src/debridge/DebridgeDlnHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ pragma solidity >=0.8.0;
/// library imports
import "forge-std/Test.sol";
import {IExternalCallExecutor} from "./interfaces/IExternalCallExecutor.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IDlnDestination, Order} from "./interfaces/IDlnDestination.sol";

/// @title Debridge DLN Helper
/// @notice helps simulate Debridge DLN message relaying with hooks
Expand All @@ -12,45 +14,7 @@ contract DebridgeDlnHelper is Test {
"CreatedOrder((uint64,bytes,uint256,bytes,uint256,uint256,bytes,uint256,bytes,bytes,bytes,bytes,bytes,bytes),bytes32,bytes,uint256,uint256,uint32,bytes)"
);

/// @dev Struct representing an order.
struct Order {
/// Nonce for each maker.
uint64 makerOrderNonce;
/// Order maker address (EOA signer for EVM) in the source chain.
bytes makerSrc;
/// Chain ID where the order's was created.
uint256 giveChainId;
/// Address of the ERC-20 token that the maker is offering as part of this order.
/// Use the zero address to indicate that the maker is offering a native blockchain token (such as Ether, Matic, etc.).
bytes giveTokenAddress;
/// Amount of tokens the maker is offering.
uint256 giveAmount;
// the ID of the chain where an order should be fulfilled.
uint256 takeChainId;
/// Address of the ERC-20 token that the maker is willing to accept on the destination chain.
bytes takeTokenAddress;
/// Amount of tokens the maker is willing to accept on the destination chain.
uint256 takeAmount;
/// Address on the destination chain where funds should be sent upon order fulfillment.
bytes receiverDst;
/// Address on the source (current) chain authorized to patch the order by adding more input tokens, making it more attractive to takers.
bytes givePatchAuthoritySrc;
/// Address on the destination chain authorized to patch the order by reducing the take amount, making it more attractive to takers,
/// and can also cancel the order in the take chain.
bytes orderAuthorityAddressDst;
// An optional address restricting anyone in the open market from fulfilling
// this order but the given address. This can be useful if you are creating a order
// for a specific taker. By default, set to empty bytes array (0x)
bytes allowedTakerDst;
// An optional address on the source (current) chain where the given input tokens
// would be transferred to in case order cancellation is initiated by the orderAuthorityAddressDst
// on the destination chain. This property can be safely set to an empty bytes array (0x):
// in this case, tokens would be transferred to the arbitrary address specified
// by the orderAuthorityAddressDst upon order cancellation
bytes allowedCancelBeneficiarySrc;
/// An optional external call data payload.
bytes externalCall;
}
address constant TAKER_ADDRESS = 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf;

struct HelpArgs {
address dlnSource;
Expand Down Expand Up @@ -206,11 +170,7 @@ contract DebridgeDlnHelper is Test {

uint256 count = args.logs.length;
for (uint256 i; i < count;) {
// https://docs.debridge.finance/dln-the-debridge-liquidity-network-protocol/interacting-with-smart-contracts/placing-orders
// CreatedOrder is the event selector for the createOrder event emitted by the DeBridge DLN contract
if (args.logs[i].topics[0] == args.eventSelector) {
vm.selectFork(args.forkId);

if (args.logs[i].emitter == args.dlnSource && args.logs[i].topics[0] == args.eventSelector) {
(
Order memory order,
bytes32 orderId,
Expand All @@ -220,43 +180,48 @@ contract DebridgeDlnHelper is Test {
uint32 reeferralCode,
bytes memory metadata
) = abi.decode(args.logs[i].data, (Order, bytes32, bytes, uint256, uint256, uint32, bytes));
DebridgeLogData memory logData = DebridgeLogData({
order: order,
orderId: orderId,
affiliateFee: affiliateFee,
nativeFixFee: nativeFixFee,
percentFee: percentFee,
reeferralCode: reeferralCode,
metadata: metadata
});

if (order.takeChainId == args.destinationChainId) {
vm.selectFork(args.forkId);

DebridgeLogData memory logData = DebridgeLogData({
order: order,
orderId: orderId,
affiliateFee: affiliateFee,
nativeFixFee: nativeFixFee,
percentFee: percentFee,
reeferralCode: reeferralCode,
metadata: metadata
});
vars.logData = logData;

// How do we fulfill the order?
// https://docs.debridge.finance/dln-the-debridge-liquidity-network-protocol/interacting-with-smart-contracts/filling-orders

// deal tokens to receiver
address receiver = address(bytes20(logData.order.receiverDst));
address token = address(bytes20(logData.order.takeTokenAddress));
if (token == address(0)) {
deal(receiver, logData.order.takeAmount);

if (logData.order.externalCall.length > 0) {
IExternalCallExecutor(receiver).onEtherReceived(
logData.orderId, receiver, logData.order.externalCall
);
}
address dlnDestinationAddress = args.dlnDestination;
address takerAddress = TAKER_ADDRESS;
address unlockAuthority = takerAddress;
uint256 fulfillAmount = order.takeAmount;
address tokenAddress = address(bytes20(order.takeTokenAddress));
bytes memory permitEnvelope = ""; // Always empty now
uint256 msgValue = 0;

if (tokenAddress == address(0)) {
// Native token transfer
msgValue = fulfillAmount;
vm.deal(takerAddress, takerAddress.balance + msgValue);
} else {
deal(token, receiver, logData.order.takeAmount);

// execute hooks
if (logData.order.externalCall.length > 0) {
IExternalCallExecutor(receiver).onERC20Received(
logData.orderId, token, logData.order.takeAmount, receiver, logData.order.externalCall
);
}
// ERC20 token transfer - Use approve instead of permit
// Ensure taker has the tokens
deal(tokenAddress, takerAddress, fulfillAmount);
// Prank as taker to approve the DlnDestination contract
vm.prank(takerAddress);
IERC20(tokenAddress).approve(dlnDestinationAddress, fulfillAmount);
}

vm.prank(takerAddress, takerAddress);
IDlnDestination(dlnDestinationAddress).fulfillOrder{value: msgValue}(
order, fulfillAmount, orderId, permitEnvelope, unlockAuthority, takerAddress
);

vm.selectFork(vars.prevForkId);
}
}

Expand All @@ -265,6 +230,8 @@ contract DebridgeDlnHelper is Test {
}
}

vm.selectFork(vars.prevForkId);
if (vm.activeFork() != vars.prevForkId) {
vm.selectFork(vars.prevForkId);
}
}
}
53 changes: 53 additions & 0 deletions src/debridge/interfaces/IDlnDestination.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/// @dev Struct representing an order.
struct Order {
/// Nonce for each maker.
uint64 makerOrderNonce;
/// Order maker address (EOA signer for EVM) in the source chain.
bytes makerSrc;
/// Chain ID where the order's was created.
uint256 giveChainId;
/// Address of the ERC-20 token that the maker is offering as part of this order.
/// Use the zero address to indicate that the maker is offering a native blockchain token (such as Ether, Matic, etc.).
bytes giveTokenAddress;
/// Amount of tokens the maker is offering.
uint256 giveAmount;
// the ID of the chain where an order should be fulfilled.
uint256 takeChainId;
/// Address of the ERC-20 token that the maker is willing to accept on the destination chain.
bytes takeTokenAddress;
/// Amount of tokens the maker is willing to accept on the destination chain.
uint256 takeAmount;
/// Address on the destination chain where funds should be sent upon order fulfillment.
bytes receiverDst;
/// Address on the source (current) chain authorized to patch the order by adding more input tokens, making it more attractive to takers.
bytes givePatchAuthoritySrc;
/// Address on the destination chain authorized to patch the order by reducing the take amount, making it more attractive to takers,
/// and can also cancel the order in the take chain.
bytes orderAuthorityAddressDst;
// An optional address restricting anyone in the open market from fulfilling
// this order but the given address. This can be useful if you are creating a order
// for a specific taker. By default, set to empty bytes array (0x)
bytes allowedTakerDst;
// An optional address on the source (current) chain where the given input tokens
// would be transferred to in case order cancellation is initiated by the orderAuthorityAddressDst
// on the destination chain. This property can be safely set to an empty bytes array (0x):
// in this case, tokens would be transferred to the arbitrary address specified
// by the orderAuthorityAddressDst upon order cancellation
bytes allowedCancelBeneficiarySrc;
/// An optional external call data payload.
bytes externalCall;
}

interface IDlnDestination {
function fulfillOrder(
Order memory _order,
uint256 _fulFillAmount,
bytes32 _orderId,
bytes calldata _permitEnvelope,
address _unlockAuthority,
address _externalCallRewardBeneficiary
) external payable;
}
3 changes: 1 addition & 2 deletions src/debridge/interfaces/IDlnSource.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ interface IDlnSource {
uint32 _referralCode,
bytes calldata _permitEnvelope
) external payable returns (bytes32 orderId);

function globalFixedNativeFee() external view returns (uint256);
}

3 changes: 2 additions & 1 deletion src/debridge/interfaces/IExternalCallExecutor.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

pragma solidity ^0.8.0;

interface IExternalCallExecutor {
/**
Expand Down
26 changes: 26 additions & 0 deletions src/debridge/libraries/DlnExternalCallLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

library DlnExternalCallLib {
struct ExternalCallEnvelopV1 {
// Address that will receive takeToken if ext call failed
address fallbackAddress;
// *optional. Smart contract that will execute ext call.
address executorAddress;
// fee that will pay for executor who will execute ext call
uint160 executionFee;
// If false, the taker must execute an external call with fulfill in a single transaction.
bool allowDelayedExecution;
// if true transaction that will execute ext call will fail if ext call is not success
bool requireSuccessfullExecution;
bytes payload;
}

struct ExternalCallPayload {
// the address of the contract to call
address to;
// *optional. Tx gas for execute ext call
uint32 txGas;
bytes callData;
}
}
5 changes: 2 additions & 3 deletions src/layerzero/LayerZeroHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,8 @@ contract LayerZeroHelper is Test {
Vm.Log memory log = logs[i];
// unsure if the default library always emits the event
if (
log
/*log.emitter == defaultLibrary &&*/
.topics[0] == eventSelector
/*log.emitter == defaultLibrary &&*/
log.topics[0] == eventSelector
) {
bytes memory payload = abi.decode(log.data, (bytes));
LayerZeroPacket.Packet memory packet = LayerZeroPacket.getPacket(payload);
Expand Down
1 change: 0 additions & 1 deletion src/wormhole/automatic-relayer/WormholeHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,6 @@ contract WormholeHelper is Test {
v.additionalVAAs = new bytes[](v.indicesCache.length - 1);
v.currIndex;


if (v.indicesCache.length > 1 && expDstAddress != address(0)) {
for (uint256 j; j < v.indicesCache.length; j++) {
log = logs[v.indicesCache[j]];
Expand Down
10 changes: 7 additions & 3 deletions test/Across.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity >=0.8.0;

import "forge-std/Test.sol";

import { console2 } from "forge-std/console2.sol";
import {console2} from "forge-std/console2.sol";

import {AcrossV3Helper} from "src/across/AcrossV3Helper.sol";
import {IAcrossSpokePoolV3} from "src/across/interfaces/IAcrossSpokePoolV3.sol";
Expand Down Expand Up @@ -134,7 +134,9 @@ contract AcrossV3HelperTest is Test {
refundChainIds[0] = L1_ID;
refundChainIds[1] = L1_ID;

acrossV3Helper.help(L1_spokePool, allDstSpokePools, RELAYER, 0, allDstForks, allDstChainIds, refundChainIds, logs);
acrossV3Helper.help(
L1_spokePool, allDstSpokePools, RELAYER, 0, allDstForks, allDstChainIds, refundChainIds, logs
);

vm.selectFork(POLYGON_FORK_ID);
assertEq(target.amount(), 12);
Expand Down Expand Up @@ -187,7 +189,9 @@ contract AcrossV3HelperTest is Test {
vm.recordLogs();
_someCrossChainFunctionInYourContract(L1_spokePool, POLYGON_ID);
Vm.Log[] memory logs = vm.getRecordedLogs();
acrossV3Helper.help(L1_spokePool, POLYGON_spokePool, RELAYER, 1736349707, POLYGON_FORK_ID, POLYGON_ID, L1_ID, logs);
acrossV3Helper.help(
L1_spokePool, POLYGON_spokePool, RELAYER, 1736349707, POLYGON_FORK_ID, POLYGON_ID, L1_ID, logs
);

vm.selectFork(POLYGON_FORK_ID);
assertEq(target.amount(), 12);
Expand Down
Loading