Skip to content

Commit

Permalink
Multi-hop helpers (#141)
Browse files Browse the repository at this point in the history
  • Loading branch information
geoff-vball authored May 23, 2024
2 parents ffcd8c1 + e169fd8 commit 9c19372
Show file tree
Hide file tree
Showing 10 changed files with 469 additions and 319 deletions.
2 changes: 1 addition & 1 deletion abi-bindings/go/ERC20Destination/ERC20Destination.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion abi-bindings/go/ERC20Source/ERC20Source.go

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion abi-bindings/go/NativeTokenSource/NativeTokenSource.go

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions contracts/src/ERC20Source.sol
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ contract ERC20Source is IERC20Source, TeleporterTokenSource {
* @dev See {IERC20Bridge-send}
*/
function send(SendTokensInput calldata input, uint256 amount) external {
_send(input, amount, false);
_send(input, amount);
}

/**
Expand All @@ -70,8 +70,7 @@ contract ERC20Source is IERC20Source, TeleporterTokenSource {
originBridgeAddress: address(this),
originSenderAddress: _msgSender(),
input: input,
amount: amount,
isMultiHop: false
amount: amount
});
}

Expand Down
5 changes: 2 additions & 3 deletions contracts/src/NativeTokenSource.sol
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ contract NativeTokenSource is INativeTokenBridge, TeleporterTokenSource {
* @dev See {INativeTokenBridge-send}
*/
function send(SendTokensInput calldata input) external payable {
_send(input, msg.value, false);
_send(input, msg.value);
}

/**
Expand All @@ -84,8 +84,7 @@ contract NativeTokenSource is INativeTokenBridge, TeleporterTokenSource {
originBridgeAddress: address(this),
originSenderAddress: _msgSender(),
input: input,
amount: msg.value,
isMultiHop: false
amount: msg.value
});
}

Expand Down
489 changes: 288 additions & 201 deletions contracts/src/TeleporterTokenDestination.sol

Large diffs are not rendered by default.

253 changes: 159 additions & 94 deletions contracts/src/TeleporterTokenSource.sol
Original file line number Diff line number Diff line change
Expand Up @@ -181,44 +181,75 @@ abstract contract TeleporterTokenSource is
* - {input.recipient} cannot be the zero address
* - {amount} must be greater than 0
*/
function _send(
function _send(SendTokensInput memory input, uint256 amount) internal sendNonReentrant {
_validateSendTokensInput(input);
// Require that a single hop transfer does not have a multi-hop fallback recipient.
require(
input.multiHopFallback == address(0),
"TeleporterTokenSource: non-zero multi-hop fallback"
);

(uint256 adjustedAmount, uint256 feeAmount) = _prepareSend({
destinationBlockchainID: input.destinationBlockchainID,
destinationBridgeAddress: input.destinationBridgeAddress,
amount: amount,
primaryFeeTokenAddress: input.primaryFeeTokenAddress,
feeAmount: input.primaryFee
});

BridgeMessage memory message = BridgeMessage({
messageType: BridgeMessageType.SINGLE_HOP_SEND,
payload: abi.encode(
SingleHopSendMessage({recipient: input.recipient, amount: adjustedAmount})
)
});

// Send message to the destination bridge address
bytes32 messageID = _sendTeleporterMessage(
TeleporterMessageInput({
destinationBlockchainID: input.destinationBlockchainID,
destinationAddress: input.destinationBridgeAddress,
feeInfo: TeleporterFeeInfo({
feeTokenAddress: input.primaryFeeTokenAddress,
amount: feeAmount
}),
requiredGasLimit: input.requiredGasLimit,
allowedRelayerAddresses: new address[](0),
message: abi.encode(message)
})
);

emit TokensSent(messageID, _msgSender(), input, adjustedAmount);
}

/**
* @notice Routes tokens from a multi-hop message to the specified destination token bridge instance.
*
* @dev Increases the bridge balance sent to each destination token bridge instance,
* and uses Teleporter to send a cross chain message. The amount passed is assumed to
* be already scaled to the local denomination for this token source.
* Requirements:
*
* - {input.destinationBlockchainID} cannot be the same as the current blockchainID
* - {input.destinationBridgeAddress} cannot be the zero address
* - {input.recipient} cannot be the zero address
* - {amount} must be greater than 0
*/
function _routeMultiHop(
SendTokensInput memory input,
uint256 amount,
bool isMultiHop
uint256 amount
) internal sendNonReentrant {
require(input.recipient != address(0), "TeleporterTokenSource: zero recipient address");
require(input.requiredGasLimit > 0, "TeleporterTokenSource: zero required gas limit");
require(input.secondaryFee == 0, "TeleporterTokenSource: non-zero secondary fee");
_validateSendTokensInput(input);

uint256 adjustedAmount;
uint256 feeAmount = input.primaryFee;
if (isMultiHop) {
adjustedAmount = _prepareMultiHopRouting(
input.destinationBlockchainID,
input.destinationBridgeAddress,
amount,
input.primaryFee
);
uint256 adjustedAmount = _prepareMultiHopRouting(
input.destinationBlockchainID, input.destinationBridgeAddress, amount, input.primaryFee
);

if (adjustedAmount == 0) {
// If the adjusted amount is zero for any reason (i.e. unsupported destination,
// being scaled down to zero, etc.), send the tokens to the multi-hop fallback.
_withdraw(input.multiHopFallback, amount);
return;
}
} else {
// Require that a single hop transfer does not have a multi-hop fallback recipient.
require(
input.multiHopFallback == address(0),
"TeleporterTokenSource: non-zero multi-hop fallback"
);
(adjustedAmount, feeAmount) = _prepareSend({
destinationBlockchainID: input.destinationBlockchainID,
destinationBridgeAddress: input.destinationBridgeAddress,
amount: amount,
primaryFeeTokenAddress: input.primaryFeeTokenAddress,
feeAmount: input.primaryFee
});
if (adjustedAmount == 0) {
// If the adjusted amount is zero for any reason (i.e. unsupported destination,
// being scaled down to zero, etc.), send the tokens to the multi-hop fallback.
_withdraw(input.multiHopFallback, amount);
return;
}

BridgeMessage memory message = BridgeMessage({
Expand All @@ -235,75 +266,91 @@ abstract contract TeleporterTokenSource is
destinationAddress: input.destinationBridgeAddress,
feeInfo: TeleporterFeeInfo({
feeTokenAddress: input.primaryFeeTokenAddress,
amount: feeAmount
amount: input.primaryFee
}),
requiredGasLimit: input.requiredGasLimit,
allowedRelayerAddresses: new address[](0),
message: abi.encode(message)
})
);

if (isMultiHop) {
emit TokensRouted(messageID, input, adjustedAmount);
} else {
emit TokensSent(messageID, _msgSender(), input, adjustedAmount);
}
emit TokensRouted(messageID, input, adjustedAmount);
}

function _sendAndCall(
bytes32 sourceBlockchainID,
address originBridgeAddress,
address originSenderAddress,
SendAndCallInput memory input,
uint256 amount,
bool isMultiHop
uint256 amount
) internal sendNonReentrant {
_validateSendAndCallInput(input);

// Require that a single hop transfer does not have a multi-hop fallback recipient.
require(
input.recipientContract != address(0),
"TeleporterTokenSource: zero recipient contract address"
);
require(input.requiredGasLimit > 0, "TeleporterTokenSource: zero required gas limit");
require(input.recipientGasLimit > 0, "TeleporterTokenSource: zero recipient gas limit");
require(
input.recipientGasLimit < input.requiredGasLimit,
"TeleporterTokenSource: invalid recipient gas limit"
);
require(
input.fallbackRecipient != address(0),
"TeleporterTokenSource: zero fallback recipient address"
input.multiHopFallback == address(0),
"TeleporterTokenSource: non-zero multi-hop fallback"
);
require(input.secondaryFee == 0, "TeleporterTokenSource: non-zero secondary fee");

uint256 adjustedAmount;
uint256 feeAmount = input.primaryFee;
if (isMultiHop) {
adjustedAmount = _prepareMultiHopRouting(
input.destinationBlockchainID,
input.destinationBridgeAddress,
amount,
input.primaryFee
);
(uint256 adjustedAmount, uint256 feeAmount) = _prepareSend({
destinationBlockchainID: input.destinationBlockchainID,
destinationBridgeAddress: input.destinationBridgeAddress,
amount: amount,
primaryFeeTokenAddress: input.primaryFeeTokenAddress,
feeAmount: input.primaryFee
});

if (adjustedAmount == 0) {
// If the adjusted amount is zero for any reason (i.e. unsupported destination,
// being scaled down to zero, etc.), send the tokens to the multi-hop fallback recipient.
_withdraw(input.multiHopFallback, amount);
return;
}
} else {
// Require that a single hop transfer does not have a multi-hop fallback recipient.
require(
input.multiHopFallback == address(0),
"TeleporterTokenSource: non-zero multi-hop fallback"
);
BridgeMessage memory message = BridgeMessage({
messageType: BridgeMessageType.SINGLE_HOP_CALL,
payload: abi.encode(
SingleHopCallMessage({
sourceBlockchainID: sourceBlockchainID,
originBridgeAddress: originBridgeAddress,
originSenderAddress: originSenderAddress,
recipientContract: input.recipientContract,
amount: adjustedAmount,
recipientPayload: input.recipientPayload,
recipientGasLimit: input.recipientGasLimit,
fallbackRecipient: input.fallbackRecipient
})
)
});

(adjustedAmount, feeAmount) = _prepareSend({
// Send message to the destination bridge address
bytes32 messageID = _sendTeleporterMessage(
TeleporterMessageInput({
destinationBlockchainID: input.destinationBlockchainID,
destinationBridgeAddress: input.destinationBridgeAddress,
amount: amount,
primaryFeeTokenAddress: input.primaryFeeTokenAddress,
feeAmount: input.primaryFee
});
destinationAddress: input.destinationBridgeAddress,
feeInfo: TeleporterFeeInfo({
feeTokenAddress: input.primaryFeeTokenAddress,
amount: feeAmount
}),
requiredGasLimit: input.requiredGasLimit,
allowedRelayerAddresses: new address[](0),
message: abi.encode(message)
})
);

emit TokensAndCallSent(messageID, originSenderAddress, input, adjustedAmount);
}

function _routeMultiHopSendAndCall(
bytes32 sourceBlockchainID,
address originBridgeAddress,
address originSenderAddress,
SendAndCallInput memory input,
uint256 amount
) internal sendNonReentrant {
_validateSendAndCallInput(input);
uint256 adjustedAmount = _prepareMultiHopRouting(
input.destinationBlockchainID, input.destinationBridgeAddress, amount, input.primaryFee
);

if (adjustedAmount == 0) {
// If the adjusted amount is zero for any reason (i.e. unsupported destination,
// being scaled down to zero, etc.), send the tokens to the multi-hop fallback recipient.
_withdraw(input.multiHopFallback, amount);
return;
}

BridgeMessage memory message = BridgeMessage({
Expand All @@ -329,19 +376,15 @@ abstract contract TeleporterTokenSource is
destinationAddress: input.destinationBridgeAddress,
feeInfo: TeleporterFeeInfo({
feeTokenAddress: input.primaryFeeTokenAddress,
amount: feeAmount
amount: input.primaryFee
}),
requiredGasLimit: input.requiredGasLimit,
allowedRelayerAddresses: new address[](0),
message: abi.encode(message)
})
);

if (isMultiHop) {
emit TokensAndCallRouted(messageID, input, adjustedAmount);
} else {
emit TokensAndCallSent(messageID, originSenderAddress, input, adjustedAmount);
}
emit TokensAndCallRouted(messageID, input, adjustedAmount);
}

/**
Expand Down Expand Up @@ -451,7 +494,7 @@ abstract contract TeleporterTokenSource is
// because the fee is taken from the amount that has already been deposited.
// For ERC20 tokens, the token address of the contract is directly passed.
// For native assets, the contract address is the wrapped token contract.
_send(
_routeMultiHop(
SendTokensInput({
destinationBlockchainID: payload.destinationBlockchainID,
destinationBridgeAddress: payload.destinationBridgeAddress,
Expand All @@ -462,8 +505,7 @@ abstract contract TeleporterTokenSource is
requiredGasLimit: payload.secondaryGasLimit,
multiHopFallback: payload.multiHopFallback
}),
sourceAmount,
true
sourceAmount
);
return;
} else if (bridgeMessage.messageType == BridgeMessageType.MULTI_HOP_CALL) {
Expand All @@ -478,7 +520,7 @@ abstract contract TeleporterTokenSource is
// because the fee is taken from the amount that has already been deposited.
// For ERC20 tokens, the token address of the contract is directly passed.
// For native assets, the contract address is the wrapped token contract.
_sendAndCall({
_routeMultiHopSendAndCall({
sourceBlockchainID: sourceBlockchainID,
originBridgeAddress: originSenderAddress,
originSenderAddress: payload.originSenderAddress,
Expand All @@ -495,8 +537,7 @@ abstract contract TeleporterTokenSource is
primaryFee: fee,
secondaryFee: 0
}),
amount: sourceAmount,
isMultiHop: true
amount: sourceAmount
});
return;
} else if (bridgeMessage.messageType == BridgeMessageType.REGISTER_DESTINATION) {
Expand Down Expand Up @@ -719,4 +760,28 @@ abstract contract TeleporterTokenSource is
require(senderBalance >= amount, "TeleporterTokenSource: insufficient bridge balance");
bridgedBalances[destinationBlockchainID][destinationBridgeAddress] = senderBalance - amount;
}

function _validateSendAndCallInput(SendAndCallInput memory input) private pure {
require(
input.recipientContract != address(0),
"TeleporterTokenSource: zero recipient contract address"
);
require(input.requiredGasLimit > 0, "TeleporterTokenSource: zero required gas limit");
require(input.recipientGasLimit > 0, "TeleporterTokenSource: zero recipient gas limit");
require(
input.recipientGasLimit < input.requiredGasLimit,
"TeleporterTokenSource: invalid recipient gas limit"
);
require(
input.fallbackRecipient != address(0),
"TeleporterTokenSource: zero fallback recipient address"
);
require(input.secondaryFee == 0, "TeleporterTokenSource: non-zero secondary fee");
}

function _validateSendTokensInput(SendTokensInput memory input) private pure {
require(input.recipient != address(0), "TeleporterTokenSource: zero recipient address");
require(input.requiredGasLimit > 0, "TeleporterTokenSource: zero required gas limit");
require(input.secondaryFee == 0, "TeleporterTokenSource: non-zero secondary fee");
}
}
Loading

0 comments on commit 9c19372

Please sign in to comment.