Transaction Hash:
Block:
17011440 at Apr-09-2023 02:52:11 PM +UTC
Transaction Fee:
0.000846369966362886 ETH
$2.06
Gas Used:
36,693 Gas / 23.066251502 Gwei
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x2d5d7d31...09a082712 | (Axelar: Gas Service) | 1.554616028118103616 Eth | 1.553136663210563638 Eth | 0.001479364907539978 | |
0x345662AB...74cCb6A90 | (Axelar: Relayer 4) |
1.307675573290822069 Eth
Nonce: 2987
|
1.306829203324459183 Eth
Nonce: 2988
| 0.000846369966362886 | |
0xA8C62111...c42632f64
Miner
| (Fee Recipient: 0xA8C...f64) | 20.824933141160691671 Eth | 20.824988180660691671 Eth | 0.0000550395 | |
0xd1a1436d...620B97368 | 0.008729561307764479 Eth | 0.010208926215304457 Eth | 0.001479364907539978 |
Execution Trace
AxelarGasServiceProxy.82ad6f35( )
AxelarGasService.refund( receiver=0xd1a1436dcC327049Ec73BC24956429c620B97368, token=0x0000000000000000000000000000000000000000, amount=1479364907539978 )
- ETH 0.001479364907539978
0xd1a1436dcc327049ec73bc24956429c620b97368.CALL( )
- ETH 0.001479364907539978
refund[AxelarGasService (ln:470)]
InvalidAddress[AxelarGasService (ln:475)]
transfer[AxelarGasService (ln:478)]
_safeTransfer[AxelarGasService (ln:480)]
NothingReceived[AxelarGasService (ln:489)]
call[AxelarGasService (ln:492)]
encodeWithSelector[AxelarGasService (ln:492)]
decode[AxelarGasService (ln:493)]
TransferFailed[AxelarGasService (ln:495)]
File 1 of 2: AxelarGasServiceProxy
File 2 of 2: AxelarGasService
// Sources flattened with hardhat v2.9.9 https://hardhat.org // File contracts/interfaces/IUpgradable.sol // SPDX-License-Identifier: MIT pragma solidity 0.8.9; // General interface for upgradable contracts interface IUpgradable { error NotOwner(); error InvalidOwner(); error InvalidCodeHash(); error InvalidImplementation(); error SetupFailed(); error NotProxy(); event Upgraded(address indexed newImplementation); event OwnershipTransferred(address indexed newOwner); // Get current owner function owner() external view returns (address); function contractId() external pure returns (bytes32); function implementation() external view returns (address); function upgrade( address newImplementation, bytes32 newImplementationCodeHash, bytes calldata params ) external; function setup(bytes calldata data) external; } // File contracts/util/Proxy.sol contract Proxy { error InvalidImplementation(); error SetupFailed(); error EtherNotAccepted(); error NotOwner(); error AlreadyInitialized(); // bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; // keccak256('owner') bytes32 internal constant _OWNER_SLOT = 0x02016836a56b71f0d02689e69e326f4f4c1b9057164ef592671cf0d37c8040c0; constructor() { // solhint-disable-next-line no-inline-assembly assembly { sstore(_OWNER_SLOT, caller()) } } function init( address implementationAddress, address newOwner, bytes memory params ) external { address owner; // solhint-disable-next-line no-inline-assembly assembly { owner := sload(_OWNER_SLOT) } if (msg.sender != owner) revert NotOwner(); if (implementation() != address(0)) revert AlreadyInitialized(); if (IUpgradable(implementationAddress).contractId() != contractId()) revert InvalidImplementation(); // solhint-disable-next-line no-inline-assembly assembly { sstore(_IMPLEMENTATION_SLOT, implementationAddress) sstore(_OWNER_SLOT, newOwner) } // solhint-disable-next-line avoid-low-level-calls (bool success, ) = implementationAddress.delegatecall( // keccak('setup(bytes)') selector abi.encodeWithSelector(0x9ded06df, params) ); if (!success) revert SetupFailed(); } // solhint-disable-next-line no-empty-blocks function contractId() internal pure virtual returns (bytes32) {} function implementation() public view returns (address implementation_) { // solhint-disable-next-line no-inline-assembly assembly { implementation_ := sload(_IMPLEMENTATION_SLOT) } } // solhint-disable-next-line no-empty-blocks function setup(bytes calldata data) public {} // solhint-disable-next-line no-complex-fallback fallback() external payable { address implementaion_ = implementation(); // solhint-disable-next-line no-inline-assembly assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), implementaion_, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } receive() external payable virtual { revert EtherNotAccepted(); } } // File contracts/gas-service/AxelarGasServiceProxy.sol contract AxelarGasServiceProxy is Proxy { function contractId() internal pure override returns (bytes32) { return keccak256('axelar-gas-service'); } }
File 2 of 2: AxelarGasService
// Sources flattened with hardhat v2.9.9 https://hardhat.org // File contracts/interfaces/IUpgradable.sol // SPDX-License-Identifier: MIT pragma solidity 0.8.9; // General interface for upgradable contracts interface IUpgradable { error NotOwner(); error InvalidOwner(); error InvalidCodeHash(); error InvalidImplementation(); error SetupFailed(); error NotProxy(); event Upgraded(address indexed newImplementation); event OwnershipTransferred(address indexed newOwner); // Get current owner function owner() external view returns (address); function contractId() external pure returns (bytes32); function implementation() external view returns (address); function upgrade( address newImplementation, bytes32 newImplementationCodeHash, bytes calldata params ) external; function setup(bytes calldata data) external; } // File contracts/interfaces/IAxelarGasService.sol // This should be owned by the microservice that is paying for gas. interface IAxelarGasService is IUpgradable { error NothingReceived(); error TransferFailed(); error InvalidAddress(); error NotCollector(); error InvalidAmounts(); event GasPaidForContractCall( address indexed sourceAddress, string destinationChain, string destinationAddress, bytes32 indexed payloadHash, address gasToken, uint256 gasFeeAmount, address refundAddress ); event GasPaidForContractCallWithToken( address indexed sourceAddress, string destinationChain, string destinationAddress, bytes32 indexed payloadHash, string symbol, uint256 amount, address gasToken, uint256 gasFeeAmount, address refundAddress ); event NativeGasPaidForContractCall( address indexed sourceAddress, string destinationChain, string destinationAddress, bytes32 indexed payloadHash, uint256 gasFeeAmount, address refundAddress ); event NativeGasPaidForContractCallWithToken( address indexed sourceAddress, string destinationChain, string destinationAddress, bytes32 indexed payloadHash, string symbol, uint256 amount, uint256 gasFeeAmount, address refundAddress ); event GasAdded(bytes32 indexed txHash, uint256 indexed logIndex, address gasToken, uint256 gasFeeAmount, address refundAddress); event NativeGasAdded(bytes32 indexed txHash, uint256 indexed logIndex, uint256 gasFeeAmount, address refundAddress); // This is called on the source chain before calling the gateway to execute a remote contract. function payGasForContractCall( address sender, string calldata destinationChain, string calldata destinationAddress, bytes calldata payload, address gasToken, uint256 gasFeeAmount, address refundAddress ) external; // This is called on the source chain before calling the gateway to execute a remote contract. function payGasForContractCallWithToken( address sender, string calldata destinationChain, string calldata destinationAddress, bytes calldata payload, string calldata symbol, uint256 amount, address gasToken, uint256 gasFeeAmount, address refundAddress ) external; // This is called on the source chain before calling the gateway to execute a remote contract. function payNativeGasForContractCall( address sender, string calldata destinationChain, string calldata destinationAddress, bytes calldata payload, address refundAddress ) external payable; // This is called on the source chain before calling the gateway to execute a remote contract. function payNativeGasForContractCallWithToken( address sender, string calldata destinationChain, string calldata destinationAddress, bytes calldata payload, string calldata symbol, uint256 amount, address refundAddress ) external payable; function addGas( bytes32 txHash, uint256 txIndex, address gasToken, uint256 gasFeeAmount, address refundAddress ) external; function addNativeGas( bytes32 txHash, uint256 logIndex, address refundAddress ) external payable; function collectFees( address payable receiver, address[] calldata tokens, uint256[] calldata amounts ) external; function refund( address payable receiver, address token, uint256 amount ) external; function gasCollector() external returns (address); } // File contracts/interfaces/IERC20.sol /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { error InvalidAccount(); /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `recipient`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address recipient, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `sender` to `recipient` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom( address sender, address recipient, uint256 amount ) external returns (bool); /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); } // File contracts/util/Upgradable.sol abstract contract Upgradable is IUpgradable { // bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; // keccak256('owner') bytes32 internal constant _OWNER_SLOT = 0x02016836a56b71f0d02689e69e326f4f4c1b9057164ef592671cf0d37c8040c0; modifier onlyOwner() { if (owner() != msg.sender) revert NotOwner(); _; } function owner() public view returns (address owner_) { // solhint-disable-next-line no-inline-assembly assembly { owner_ := sload(_OWNER_SLOT) } } function transferOwnership(address newOwner) external virtual onlyOwner { if (newOwner == address(0)) revert InvalidOwner(); emit OwnershipTransferred(newOwner); // solhint-disable-next-line no-inline-assembly assembly { sstore(_OWNER_SLOT, newOwner) } } function implementation() public view returns (address implementation_) { // solhint-disable-next-line no-inline-assembly assembly { implementation_ := sload(_IMPLEMENTATION_SLOT) } } function upgrade( address newImplementation, bytes32 newImplementationCodeHash, bytes calldata params ) external override onlyOwner { if (IUpgradable(newImplementation).contractId() != IUpgradable(this).contractId()) revert InvalidImplementation(); if (newImplementationCodeHash != newImplementation.codehash) revert InvalidCodeHash(); if (params.length > 0) { // solhint-disable-next-line avoid-low-level-calls (bool success, ) = newImplementation.delegatecall(abi.encodeWithSelector(this.setup.selector, params)); if (!success) revert SetupFailed(); } emit Upgraded(newImplementation); // solhint-disable-next-line no-inline-assembly assembly { sstore(_IMPLEMENTATION_SLOT, newImplementation) } } function setup(bytes calldata data) external override { // Prevent setup from being called on the implementation if (implementation() == address(0)) revert NotProxy(); _setup(data); } // solhint-disable-next-line no-empty-blocks function _setup(bytes calldata data) internal virtual {} } // File contracts/gas-service/AxelarGasService.sol // This should be owned by the microservice that is paying for gas. contract AxelarGasService is Upgradable, IAxelarGasService { address public immutable gasCollector; constructor(address gasCollector_) { gasCollector = gasCollector_; } modifier onlyCollector() { if (msg.sender != gasCollector) revert NotCollector(); _; } // This is called on the source chain before calling the gateway to execute a remote contract. function payGasForContractCall( address sender, string calldata destinationChain, string calldata destinationAddress, bytes calldata payload, address gasToken, uint256 gasFeeAmount, address refundAddress ) external override { _safeTransferFrom(gasToken, msg.sender, gasFeeAmount); emit GasPaidForContractCall( sender, destinationChain, destinationAddress, keccak256(payload), gasToken, gasFeeAmount, refundAddress ); } // This is called on the source chain before calling the gateway to execute a remote contract. function payGasForContractCallWithToken( address sender, string calldata destinationChain, string calldata destinationAddress, bytes calldata payload, string memory symbol, uint256 amount, address gasToken, uint256 gasFeeAmount, address refundAddress ) external override { _safeTransferFrom(gasToken, msg.sender, gasFeeAmount); emit GasPaidForContractCallWithToken( sender, destinationChain, destinationAddress, keccak256(payload), symbol, amount, gasToken, gasFeeAmount, refundAddress ); } // This is called on the source chain before calling the gateway to execute a remote contract. function payNativeGasForContractCall( address sender, string calldata destinationChain, string calldata destinationAddress, bytes calldata payload, address refundAddress ) external payable override { if (msg.value == 0) revert NothingReceived(); emit NativeGasPaidForContractCall(sender, destinationChain, destinationAddress, keccak256(payload), msg.value, refundAddress); } // This is called on the source chain before calling the gateway to execute a remote contract. function payNativeGasForContractCallWithToken( address sender, string calldata destinationChain, string calldata destinationAddress, bytes calldata payload, string calldata symbol, uint256 amount, address refundAddress ) external payable override { if (msg.value == 0) revert NothingReceived(); emit NativeGasPaidForContractCallWithToken( sender, destinationChain, destinationAddress, keccak256(payload), symbol, amount, msg.value, refundAddress ); } function addGas( bytes32 txHash, uint256 logIndex, address gasToken, uint256 gasFeeAmount, address refundAddress ) external override { _safeTransferFrom(gasToken, msg.sender, gasFeeAmount); emit GasAdded(txHash, logIndex, gasToken, gasFeeAmount, refundAddress); } function addNativeGas( bytes32 txHash, uint256 logIndex, address refundAddress ) external payable override { if (msg.value == 0) revert NothingReceived(); emit NativeGasAdded(txHash, logIndex, msg.value, refundAddress); } function collectFees( address payable receiver, address[] calldata tokens, uint256[] calldata amounts ) external onlyCollector { if (receiver == address(0)) revert InvalidAddress(); uint256 tokensLength = tokens.length; if (tokensLength != amounts.length) revert InvalidAmounts(); for (uint256 i; i < tokensLength; i++) { address token = tokens[i]; uint256 amount = amounts[i]; if (amount == 0) revert InvalidAmounts(); if (token == address(0)) { if (amount <= address(this).balance) receiver.transfer(amount); } else { if (amount <= IERC20(token).balanceOf(address(this))) _safeTransfer(token, receiver, amount); } } } function refund( address payable receiver, address token, uint256 amount ) external onlyCollector { if (receiver == address(0)) revert InvalidAddress(); if (token == address(0)) { receiver.transfer(amount); } else { _safeTransfer(token, receiver, amount); } } function _safeTransfer( address tokenAddress, address receiver, uint256 amount ) internal { if (amount == 0) revert NothingReceived(); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returnData) = tokenAddress.call(abi.encodeWithSelector(IERC20.transfer.selector, receiver, amount)); bool transferred = success && (returnData.length == uint256(0) || abi.decode(returnData, (bool))); if (!transferred || tokenAddress.code.length == 0) revert TransferFailed(); } function _safeTransferFrom( address tokenAddress, address from, uint256 amount ) internal { if (amount == 0) revert NothingReceived(); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returnData) = tokenAddress.call( abi.encodeWithSelector(IERC20.transferFrom.selector, from, address(this), amount) ); bool transferred = success && (returnData.length == uint256(0) || abi.decode(returnData, (bool))); if (!transferred || tokenAddress.code.length == 0) revert TransferFailed(); } function contractId() external pure returns (bytes32) { return keccak256('axelar-gas-service'); } }