Skip to content

Commit

Permalink
pay contract: pay axelar gas using native token
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewliu08 committed Oct 31, 2024
1 parent b22f500 commit 1618dfb
Show file tree
Hide file tree
Showing 13 changed files with 705 additions and 556 deletions.
11 changes: 5 additions & 6 deletions packages/contract/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@ full:
make build

test:
# $(eval BASE_MAINNET_RPC ?= $(or $(BASE_MAINNET_RPC),https://base-rpc.publicnode.com)) # Use environment variable if set, otherwise use default
# @echo Running tests with RPC: $(BASE_MAINNET_RPC)
# forge test -vvv --fork-url "$(BASE_MAINNET_RPC)" --no-match-path "test/uniswap/*"
# forge test -vvv --fork-url "$(BASE_MAINNET_RPC)" --fork-block-number 14513720 --match-path "test/uniswap/*" --no-match-path "test/uniswap/Quoter.t.sol"
# forge test -vvv --fork-url "$(BASE_MAINNET_RPC)" --fork-block-number 15950101 --match-path "test/uniswap/Quoter.t.sol"
forge test -vvv --match-path "test/DaimoPay.t.sol"
$(eval BASE_MAINNET_RPC ?= $(or $(BASE_MAINNET_RPC),https://base-rpc.publicnode.com)) # Use environment variable if set, otherwise use default
@echo Running tests with RPC: $(BASE_MAINNET_RPC)
forge test -vvv --fork-url "$(BASE_MAINNET_RPC)" --no-match-path "test/uniswap/*"
forge test -vvv --fork-url "$(BASE_MAINNET_RPC)" --fork-block-number 14513720 --match-path "test/uniswap/*" --no-match-path "test/uniswap/Quoter.t.sol"
forge test -vvv --fork-url "$(BASE_MAINNET_RPC)" --fork-block-number 15950101 --match-path "test/uniswap/Quoter.t.sol"

coverage:
$(eval BASE_MAINNET_RPC ?= $(or $(BASE_MAINNET_RPC),https://base-rpc.publicnode.com)) # Use environment variable if set, otherwise use default
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

This file was deleted.

This file was deleted.

Large diffs are not rendered by default.

16 changes: 7 additions & 9 deletions packages/contract/script/pay/DeployDaimoPayAxelarBridger.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import "forge-std/Script.sol";
import "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol";

import "../../src/pay/DaimoPayAxelarBridger.sol";
import "../../src/pay/DaimoPayAxelarReceiver.sol";
import "../Constants.s.sol";

contract DeployDaimoPayAxelarBridger is Script {
Expand All @@ -24,7 +23,7 @@ contract DeployDaimoPayAxelarBridger is Script {
address initOwner = msg.sender;

address bridger = CREATE3.deploy(
keccak256("DaimoPayAxelarBridger-test3"),
keccak256("DaimoPayAxelarBridger-new2"),
abi.encodePacked(
type(DaimoPayAxelarBridger).creationCode,
abi.encode(
Expand Down Expand Up @@ -54,7 +53,7 @@ contract DeployDaimoPayAxelarBridger is Script {
{
address axelarReceiver = CREATE3.getDeployed(
msg.sender,
keccak256("DaimoPayAxelarReceiver-test3")
keccak256("DaimoPayAxelarBridger-new2")
);

bool testnet = _isTestnet(block.chainid);
Expand All @@ -76,12 +75,11 @@ contract DeployDaimoPayAxelarBridger is Script {
block.chainid == OP_MAINNET ||
block.chainid == POLYGON_MAINNET
) {
chainIds = new uint256[](2);
toTokens = new address[](2);
bridgeRoutes = new DaimoPayAxelarBridger.AxelarBridgeRoute[](2);
chainIds = new uint256[](1);
toTokens = new address[](1);
bridgeRoutes = new DaimoPayAxelarBridger.AxelarBridgeRoute[](1);

chainIds[0] = BNB_MAINNET;
chainIds[1] = OP_MAINNET;

for (uint32 i = 0; i < chainIds.length; ++i) {
toTokens[i] = _getAxlUSDCAddress(chainIds[i]);
Expand All @@ -90,7 +88,7 @@ contract DeployDaimoPayAxelarBridger is Script {
tokenSymbol: "axlUSDC",
localTokenAddr: _getAxlUSDCAddress(block.chainid),
receiverContract: axelarReceiver,
fee: 1_000_000 // 1 USDC
fee: 300000000000000 // 0.0003 ETH
});
}
} else if (block.chainid == BNB_MAINNET) {
Expand All @@ -112,7 +110,7 @@ contract DeployDaimoPayAxelarBridger is Script {
tokenSymbol: "axlUSDC",
localTokenAddr: _getAxlUSDCAddress(block.chainid),
receiverContract: axelarReceiver,
fee: 1_000_000 // 1 USDC
fee: 300000000000000 // 0.0003 ETH
});
}
} else {
Expand Down
31 changes: 0 additions & 31 deletions packages/contract/script/pay/DeployDaimoPayAxelarReceiver.s.sol

This file was deleted.

2 changes: 1 addition & 1 deletion packages/contract/script/pay/DeployDaimoPayRelayer.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ contract DeployDaimoPayRelayer is Script {
vm.startBroadcast();

address daimoPayRelayer = CREATE3.deploy(
keccak256("DaimoPayRelayer-test11"),
keccak256("DaimoPayRelayer-new2"),
abi.encodePacked(
type(DaimoPayRelayer).creationCode,
abi.encode(owner)
Expand Down
88 changes: 67 additions & 21 deletions packages/contract/src/pay/DaimoPayAxelarBridger.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.12;

import {AxelarExpressExecutableWithToken} from "@axelar-network/contracts/express/AxelarExpressExecutableWithToken.sol";
import {IAxelarGatewayWithToken} from "@axelar-network/contracts/interfaces/IAxelarGatewayWithToken.sol";
import {IAxelarGasService} from "@axelar-network/contracts/interfaces/IAxelarGasService.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
Expand All @@ -17,7 +18,11 @@ import "../interfaces/IDaimoPayBridger.sol";
/// @dev Bridges assets from to a destination chain using Axelar Protocol. Makes
/// the assumption that the local token is an ERC20 token and has a 1 to 1 price
/// with the corresponding destination token.
contract DaimoPayAxelarBridger is IDaimoPayBridger, Ownable2Step {
contract DaimoPayAxelarBridger is
IDaimoPayBridger,
AxelarExpressExecutableWithToken,
Ownable2Step
{
using SafeERC20 for IERC20;
using Strings for address;

Expand All @@ -26,6 +31,7 @@ contract DaimoPayAxelarBridger is IDaimoPayBridger, Ownable2Step {
string tokenSymbol;
address localTokenAddr;
address receiverContract;
// Fee to be paid in native token for Axelar's bridging gas fee
uint256 fee;
}

Expand Down Expand Up @@ -66,7 +72,10 @@ contract DaimoPayAxelarBridger is IDaimoPayBridger, Ownable2Step {
uint256[] memory _toChainIds,
address[] memory _toTokens,
AxelarBridgeRoute[] memory _bridgeRoutes
) Ownable(_owner) {
)
Ownable(_owner)
AxelarExpressExecutableWithToken(address(_axelarGateway))
{
axelarGateway = _axelarGateway;
axelarGasService = _axelarGasService;

Expand Down Expand Up @@ -135,11 +144,40 @@ contract DaimoPayAxelarBridger is IDaimoPayBridger, Ownable2Step {
});
}

// ----- AXELAR EXECUTABLE FUNCTIONS -----

/// Part of the AxelarExpressExecutableWithToken interface. Used to make
/// a contract call on the destination chain without tokens. Not supported
/// by this implementation because we will always be bridging tokens.
function _execute(
bytes32 /* commandId */,
string calldata /* sourceChain */,
string calldata /* sourceAddress */,
bytes calldata /* payload */
) internal pure override {
revert("DPAxB: _execute not supported");
}

/// Part of the AxelarExpressExecutableWithToken interface. Used to make
/// a contract call on the destination chain with tokens. Will always be
/// used to transfer tokens to the intent address on the destination chain.
function _executeWithToken(
bytes32 /* commandId */,
string calldata /* sourceChain */,
string calldata /* sourceAddress */,
bytes calldata payload,
string calldata tokenSymbol,
uint256 amount
) internal override {
address recipient = abi.decode(payload, (address));
address tokenAddress = axelarGateway.tokenAddresses(tokenSymbol);

IERC20(tokenAddress).safeTransfer(recipient, amount);
}

// ----- BRIDGING FUNCTIONS -----

/// Get the local token that corresponds to the destination token. Get the
/// minimum input amount for a given output amount. The input amount must
/// cover the max of the percentage fee and the flat fee.
/// Get the local token that corresponds to the destination token.
function getInputTokenAmount(
uint256 toChainId,
address toToken,
Expand All @@ -148,20 +186,22 @@ contract DaimoPayAxelarBridger is IDaimoPayBridger, Ownable2Step {
AxelarBridgeRoute memory bridgeRoute = bridgeRouteMapping[toChainId][
toToken
];
return (bridgeRoute.localTokenAddr, toAmount + bridgeRoute.fee);
return (bridgeRoute.localTokenAddr, toAmount);
}

/// Initiate a bridge to a destination chain using Across Protocol.
/// Initiate a bridge to a destination chain using Axelar Protocol.
function sendToChain(
uint256 toChainId,
address toAddress,
address toToken,
uint256 toAmount,
bytes calldata /* extraData */
bytes calldata extraData
) public {
require(toChainId != block.chainid, "DPAxB: same chain");
require(toAmount > 0, "DPAxB: zero amount");

address refundAddress = abi.decode(extraData, (address));

// Get the local token that corresponds to the destination token.
(address inputToken, uint256 inputAmount) = getInputTokenAmount({
toChainId: toChainId,
Expand All @@ -173,36 +213,40 @@ contract DaimoPayAxelarBridger is IDaimoPayBridger, Ownable2Step {
toToken
];

// Move input token from caller to this contract and approve the
// AxelarGateway contract.
// Move input token from caller to this contract
IERC20(inputToken).safeTransferFrom({
from: msg.sender,
to: address(this),
value: inputAmount
});
IERC20(inputToken).forceApprove({
spender: address(axelarGateway),
value: inputAmount
});

axelarGasService.payGasForExpressCallWithToken(
msg.sender,
// Pay for Axelar's bridging gas fee.
axelarGasService.payNativeGasForContractCallWithToken{
value: bridgeRoute.fee
}(
address(this),
bridgeRoute.destChainName,
bridgeRoute.receiverContract.toHexString(),
abi.encode(toAddress),
bridgeRoute.tokenSymbol,
inputAmount,
inputToken,
bridgeRoute.fee,
msg.sender
toAmount,
refundAddress
);

// Approve the AxelarGateway contract and initiate the bridge. Send the
// tokens to the DaimoPayAxelarBridger on the destination chain. The
// _executeWithToken function will be called on the destination chain
// to transfer the tokens to the toAddress.
IERC20(inputToken).forceApprove({
spender: address(axelarGateway),
value: toAmount
});
axelarGateway.callContractWithToken(
bridgeRoute.destChainName,
bridgeRoute.receiverContract.toHexString(),
abi.encode(toAddress),
bridgeRoute.tokenSymbol,
inputAmount
toAmount
);

emit BridgeInitiated({
Expand All @@ -215,4 +259,6 @@ contract DaimoPayAxelarBridger is IDaimoPayBridger, Ownable2Step {
toAmount: toAmount
});
}

receive() external payable {}
}
47 changes: 0 additions & 47 deletions packages/contract/src/pay/DaimoPayAxelarReceiver.sol

This file was deleted.

20 changes: 20 additions & 0 deletions packages/contract/src/pay/DaimoPayRelayer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,26 @@ contract DaimoPayRelayer is Ownable2Step {
);
}

function startIntent(
DaimoPay dp,
PayIntent calldata intent,
Call[] calldata calls,
bytes calldata bridgeExtraData,
uint256 bridgeGasFee
) public onlyOwner {
// We use Axelar when bridging to/from BSC. Axelar requries a native token
// payment for the gas fee.
if (block.chainid == 56 || intent.toChainId == 56) {
DaimoPayBridger bridger = dp.bridger();
IDaimoPayBridger axelarBridger = bridger.chainIdToBridger(56);
(bool success, ) = address(axelarBridger).call{value: bridgeGasFee}(
""
);
require(success, "DPR: axelar fee transfer failed");
}
dp.startIntent(intent, calls, bridgeExtraData);
}

function fastFinish(
DaimoPay dp,
PayIntent calldata intent,
Expand Down
Loading

0 comments on commit 1618dfb

Please sign in to comment.