-
Notifications
You must be signed in to change notification settings - Fork 23
Implement requestRelay in Axelar gateway #200
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
base: master
Are you sure you want to change the base?
Changes from all commits
6018095
d3b39cd
2ace4a6
d6705e7
cee3a1c
01aac23
541c47b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should document somewhere that the Does this behavior make sense? |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,8 @@ pragma solidity ^0.8.27; | |
import {InteroperableAddress} from "@openzeppelin/contracts/utils/draft-InteroperableAddress.sol"; | ||
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; | ||
import {IERC7786GatewaySource} from "../../interfaces/IERC7786.sol"; | ||
import {IERC7786Attributes} from "../../interfaces/IERC7786Attributes.sol"; | ||
import {ERC7786Attributes} from "../utils/ERC7786Attributes.sol"; | ||
import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; | ||
|
||
/** | ||
|
@@ -15,13 +17,21 @@ import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; | |
*/ | ||
abstract contract AxelarGatewaySource is IERC7786GatewaySource, AxelarGatewayBase { | ||
using InteroperableAddress for bytes; | ||
using Strings for address; | ||
|
||
struct MessageDetails { | ||
string destination; | ||
string target; | ||
bytes payload; | ||
} | ||
|
||
uint256 private _lastSendId; | ||
mapping(bytes32 => MessageDetails) private _details; | ||
|
||
error UnsupportedNativeTransfer(); | ||
|
||
/// @inheritdoc IERC7786GatewaySource | ||
function supportsAttribute(bytes4 /*selector*/) public pure returns (bool) { | ||
return false; | ||
function supportsAttribute(bytes4 selector) public pure returns (bool) { | ||
return selector == IERC7786Attributes.requestRelay.selector; | ||
} | ||
|
||
/// @inheritdoc IERC7786GatewaySource | ||
|
@@ -30,29 +40,71 @@ abstract contract AxelarGatewaySource is IERC7786GatewaySource, AxelarGatewayBas | |
bytes calldata payload, | ||
bytes[] calldata attributes | ||
) external payable returns (bytes32 sendId) { | ||
require(msg.value == 0, UnsupportedNativeTransfer()); | ||
// Use of `if () revert` syntax to avoid accessing attributes[0] if it's empty | ||
if (attributes.length > 0) | ||
revert UnsupportedAttribute(attributes[0].length < 0x04 ? bytes4(0) : bytes4(attributes[0][0:4])); | ||
// Process attributes (relay) | ||
bool withRelay = false; | ||
uint256 value = 0; | ||
address refundRecipient = address(0); | ||
|
||
for (uint256 i = 0; i < attributes.length; ++i) { | ||
(withRelay, value, , refundRecipient) = ERC7786Attributes.tryDecodeRequestRelayCalldata(attributes[i]); | ||
require(withRelay, UnsupportedAttribute(attributes[i].length < 0x04 ? bytes4(0) : bytes4(attributes[i]))); | ||
} | ||
if (!withRelay) { | ||
sendId = bytes32(++_lastSendId); | ||
} | ||
require(msg.value == value, UnsupportedNativeTransfer()); | ||
|
||
// Create the package | ||
bytes memory sender = InteroperableAddress.formatEvmV1(block.chainid, msg.sender); | ||
bytes memory adapterPayload = abi.encode(sender, recipient, payload); | ||
|
||
// Emit event | ||
sendId = bytes32(0); // Explicitly set to 0 | ||
// Emit event early (stack too deep) | ||
emit MessageSent(sendId, sender, recipient, payload, 0, attributes); | ||
|
||
// Send the message | ||
(bytes2 chainType, bytes calldata chainReference, ) = recipient.parseV1Calldata(); | ||
string memory axelarDestination = getAxelarChain(InteroperableAddress.formatV1(chainType, chainReference, "")); | ||
bytes memory remoteGateway = getRemoteGateway(chainType, chainReference); | ||
_axelarGateway.callContract( | ||
axelarDestination, | ||
address(bytes20(remoteGateway)).toChecksumHexString(), // TODO non-evm chains? | ||
adapterPayload | ||
); | ||
string memory axelarDestination = getAxelarChain(InteroperableAddress.formatV1(chainType, chainReference, "")); | ||
// TODO: How should we "stringify" addresses on non-evm chains. Axelar doesn't yet support hex format for all | ||
// non evm addresses. Do we want to use Hex? Base58? Base64? | ||
string memory axelarTarget = chainType == 0x0000 | ||
? Strings.toChecksumHexString(address(bytes20(remoteGateway))) | ||
: Strings.toHexString(remoteGateway); | ||
|
||
return sendId; | ||
if (withRelay) { | ||
_axelarGasService.payNativeGasForContractCall{value: msg.value}( | ||
address(this), | ||
axelarDestination, | ||
axelarTarget, | ||
adapterPayload, | ||
refundRecipient | ||
); | ||
} else { | ||
_details[sendId] = MessageDetails(axelarDestination, axelarTarget, adapterPayload); | ||
} | ||
|
||
_axelarGateway.callContract(axelarDestination, axelarTarget, adapterPayload); | ||
} | ||
|
||
/** | ||
* @dev Request relaying of a message initiated using `sendMessage`. | ||
* | ||
* NOTE: AxelarGasService does NOT take a gasLimit. Instead it uses the msg.value sent to determine the gas limit. | ||
* This function ignores the provided `gasLimit` parameter. | ||
*/ | ||
function requestRelay(bytes32 sendId, uint256 /*gasLimit*/, address refundRecipient) external payable { | ||
Amxx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
MessageDetails memory details = _details[sendId]; | ||
require(details.payload.length > 0); | ||
|
||
// delete storage for some refund | ||
delete _details[sendId]; | ||
|
||
_axelarGasService.payNativeGasForContractCall{value: msg.value}( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it okay to call this function after There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @cjcobb23 what is the interface to pay gas AFTER a message is sent ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as I know it's the It really seems like there's no way to implement |
||
address(this), | ||
details.destination, | ||
details.target, | ||
details.payload, | ||
refundRecipient | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.27; | ||
|
||
import {IERC7786Attributes} from "../../interfaces/IERC7786Attributes.sol"; | ||
|
||
/// @dev Library of helper to parse/process ERC-7786 attributes | ||
library ERC7786Attributes { | ||
/// @dev Parse the `requestRelay(uint256,uint256,address)` (0x4cbb573a) attribute into its components. | ||
function tryDecodeRequestRelay( | ||
Amxx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
bytes memory attribute | ||
) internal pure returns (bool success, uint256 value, uint256 gasLimit, address refundRecipient) { | ||
success = bytes4(attribute) == IERC7786Attributes.requestRelay.selector && attribute.length >= 0x64; | ||
|
||
assembly ("memory-safe") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this technically unsafe as we're potentially reading out of bounds? Although we are multiplying by 0 in that case... so I think this is probably ok. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, multiply by zero is here for security. Any "unsafe" access should be nullified. If the content being read is irrelevant, I don't see how that could be a problem. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would definitely be a problem with traditional undefined behavior, but I don't think that's what happens with unsafe memory accesses in solc... |
||
value := mul(success, mload(add(attribute, 0x24))) | ||
gasLimit := mul(success, mload(add(attribute, 0x44))) | ||
refundRecipient := mul(success, mload(add(attribute, 0x64))) | ||
} | ||
} | ||
|
||
/// @dev Calldata variant of {tryDecodeRequestRelay}. | ||
function tryDecodeRequestRelayCalldata( | ||
bytes calldata attribute | ||
) internal pure returns (bool success, uint256 value, uint256 gasLimit, address refundRecipient) { | ||
success = bytes4(attribute) == IERC7786Attributes.requestRelay.selector && attribute.length >= 0x64; | ||
|
||
assembly ("memory-safe") { | ||
value := mul(success, calldataload(add(attribute.offset, 0x04))) | ||
gasLimit := mul(success, calldataload(add(attribute.offset, 0x24))) | ||
refundRecipient := mul(success, calldataload(add(attribute.offset, 0x44))) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.4; | ||
|
||
/** | ||
* @dev Standard attributes for ERC-7786. These attributes may be standardized in different ERCs. | ||
*/ | ||
interface IERC7786Attributes { | ||
function requestRelay(uint256 value, uint256 gasLimit, address refundRecipient) external; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.27; | ||
|
||
contract AxelarGasServiceMock { | ||
event NativeGasPaidForContractCall( | ||
address indexed sourceAddress, | ||
string destinationChain, | ||
string destinationAddress, | ||
bytes32 indexed payloadHash, | ||
uint256 gasFeeAmount, | ||
address refundAddress | ||
); | ||
|
||
function payNativeGasForContractCall( | ||
address sender, | ||
string calldata destinationChain, | ||
string calldata destinationAddress, | ||
bytes calldata payload, | ||
address refundAddress | ||
) external payable { | ||
emit NativeGasPaidForContractCall( | ||
sender, | ||
destinationChain, | ||
destinationAddress, | ||
keccak256(payload), | ||
msg.value, | ||
refundAddress | ||
); | ||
} | ||
} |
This file was deleted.
This file was deleted.
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.