Skip to content
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

contract: intent addrs #1351

Closed
wants to merge 7 commits into from
Closed
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

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

129 changes: 129 additions & 0 deletions packages/contract/src/DaimoPayCCTPBridger.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.12;

import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import "openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol";
import "openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol";

import "./interfaces/IDaimoPayBridger.sol";
import "./DaimoFastCCTP.sol";

/// @title Bridger implementation for Circle's Cross-Chain Transfer Protocol (CCTP)
/// @author The Daimo team
/// @custom:security-contact [email protected]
///
/// Bridges assets from to a destination chain using CCTP. The only supported
/// bridge token is USDC.
contract DaimoPayCCTPBridger is
IDaimoPayBridger,
Ownable2StepUpgradeable,
UUPSUpgradeable
{
using SafeERC20 for IERC20;

// CCTP TokenMessenger for this chain.
ICCTPTokenMessenger public immutable cctpMessenger;

// Map chainID to CCTP domain. CCTP uses 0 as a domain. In order to use
// 0 as a not-found value, store CCTP domain + 1 in the mapping.
// 0 = not found, 1 = Ethereum, 2 = Avalanche, etc.
mapping(uint256 chainId => uint32 domain) public cctpDomainMapping;

event DomainAdded(uint256 indexed chainID, uint32 domain);
event BridgeInitiated(
address indexed sender,
IERC20 indexed tokenIn,
uint256 amountIn,
uint256 toChainID
);

constructor() {
_disableInitializers();
}

// ----- ADMIN FUNCTIONS -----

/// Initialize. Specify owner (not msg.sender) to allow CREATE3 deployment.
function init(
address _initialOwner,
ICCTPTokenMessenger _cctpMessenger,
uint256[] memory _cctpChainIDs,
uint32[] memory _cctpDomains
) public initializer {
__Ownable_init(_initialOwner);

cctpMessenger = _cctpMessenger;

uint256 n = _cctpChainIDs.length;
require(n == _cctpDomains.length, "DPCCTPB: wrong cctpDomains length");

for (uint256 i = 0; i < n; ++i) {
_addDomain(_cctpChainIDs[i], _cctpDomains[i]);
}
}

/// UUPSUpsgradeable: only allow owner to upgrade
function _authorizeUpgrade(address) internal view override onlyOwner {}

/// UUPSUpgradeable: expose implementation
function implementation() public view returns (address) {
return ERC1967Utils.getImplementation();
}

/// Adds a new supported CCTP recipient chain.
function addCCTPDomain(
uint256 chainID,
uint32 domain
) public onlyOwner {
_addDomain(chainID, domain);
}

function _addDomain(uint256 chainID, uint32 domain) private {
require(chainID != 0, "DPCCTPB: missing chainID");
// CCTP uses 0 as a domain. In order to use 0 as a not-found value,
// store CCTP domain + 1 in the mapping.
cctpDomainMapping[chainID] = domain + 1;
emit DomainAdded(chainID, domain);
}

function _getDomain(uint256 chainID) internal view returns (uint32) {
uint32 domain = cctpDomainMapping[chainID];
// The mapping stores CCTP domain + 1 and reserves 0 for not-found.
require(domain != 0, "DPCCTPB: missing domain");
return domain - 1;
}

function addressToBytes32(address addr) internal pure returns (bytes32) {
return bytes32(uint256(uint160(addr)));
}

// ----- PUBLIC FUNCTIONS -----

/// Initiates a bridge to a destination chain using CCTP.
function sendToChain(
IERC20 tokenIn,
uint256 amountIn,
uint256 toChainID,
bytes calldata /* extraData */
) public {
// Move input token from caller to this contract and approve CCTP.
tokenIn.safeTransferFrom({
from: msg.sender,
to: address(this),
value: amountIn
});
tokenIn.forceApprove({spender: address(cctpMessenger), value: amountIn});

uint32 domain = _getDomain(toChainID);

cctpMessenger.depositForBurn({
amount: amountIn,
destinationDomain: domain,
mintRecipient: addressToBytes32(address(this)),
burnToken: address(tokenIn)
});

emit BridgeInitiated(msg.sender, tokenIn, amountIn, toChainID);
}
}
3 changes: 0 additions & 3 deletions packages/contract/src/interfaces/IDaimoBridger.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.12;

import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";

/// Bridges assets automatically. Specifically, it lets any market maker
Expand Down
27 changes: 27 additions & 0 deletions packages/contract/src/interfaces/IDaimoPayBridger.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.12;

import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";

/// @notice Bridges assets automatically. Specifically, it lets any market maker
/// initiate a bridge transaction to another chain.
interface IDaimoPayBridger {
/// @notice Emitted when a bridge transaction is initiated
event BridgeInitiated(
address indexed sender,
uint256 indexed toChainId,
address indexed toAddress,
address toToken,
uint256 toAmount
);

/// @dev Initiate a bridge. Guarantees that (toToken, toAmount) shows up
/// in (toAddress) on (toChainId). Otherwise, reverts.
function sendToChain(
uint256 toChainId,
address toAddress,
address toToken,
uint256 toAmount,
bytes calldata extraData
) external;
}
87 changes: 87 additions & 0 deletions packages/contract/src/pay/DaimoPayAcrossBridger.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.12;

import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import "openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol";
import "openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol";

import "../interfaces/IDaimoPayBridger.sol";
import "../../vendor/across/V3SpokePoolInterface.sol";

/// @title Bridger implementation for Across Protocol
/// @author The Daimo team
/// @custom:security-contact [email protected]
///
/// Bridges assets from to a destination chain using Across Protocol.
contract DaimoPayAcrossBridger is IDaimoPayBridger {
using SafeERC20 for IERC20;

struct ExtraData {
address fromToken;
uint256 fromAmount;
address exclusiveRelayer;
uint32 quoteTimestamp;
uint32 fillDeadline;
uint32 exclusivityDeadline;
bytes message;
}

// SpokePool contract address for this chain.
V3SpokePoolInterface public immutable spokePool;

constructor(V3SpokePoolInterface _spokePool) {
spokePool = _spokePool;
}

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

// Parse remaining arguments from extraData
ExtraData memory extra;
extra = abi.decode(extraData, (ExtraData));

// Move input token from caller to this contract and approve the
// SpokePool contract.
IERC20(extra.fromToken).safeTransferFrom({
from: msg.sender,
to: address(this),
value: extra.fromAmount
});
IERC20(extra.fromToken).forceApprove({
spender: address(spokePool),
value: extra.fromAmount
});

spokePool.depositV3({
depositor: address(this),
recipient: toAddress,
inputToken: extra.fromToken,
outputToken: toToken,
inputAmount: extra.fromAmount,
outputAmount: toAmount,
destinationChainId: toChainId,
exclusiveRelayer: extra.exclusiveRelayer,
quoteTimestamp: extra.quoteTimestamp,
fillDeadline: extra.fillDeadline,
exclusivityDeadline: extra.exclusivityDeadline,
message: extra.message
});

emit BridgeInitiated(
msg.sender,
toChainId,
toAddress,
toToken,
toAmount
);
}
}
96 changes: 96 additions & 0 deletions packages/contract/src/pay/DaimoPayBridger.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.12;

import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import "openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol";
import "openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol";

import "./interfaces/IDaimoPayBridger.sol";
import "./pay/TokenUtils.sol";

/// @title Bridger which multiplexes between different bridging protocols
/// @author The Daimo team
/// @custom:security-contact [email protected]
///
/// Bridges assets from to a supported destination chain. Multiplexes between
/// different bridging protocols by destination chain.
contract DaimoPayBridger is IDaimoPayBridger {
using SafeERC20 for IERC20;

address public immutable owner;

// Map chainId to the contract address of an IDaimoPayBridger implementation
mapping(uint256 chainId => IDaimoPayBridger bridger) public bridgerMapping;

event BridgeAdded(uint256 indexed chainId, address bridger);

/// Specify owner (not msg.sender) to allow CREATE3 deployment.
constructor(address _initialOwner) {
owner = _initialOwner;
}

modifier onlyOwner() {
require(msg.sender == owner, "DPB: caller is not the owner");
_;
}

// ----- ADMIN FUNCTIONS -----

/// Initialize. Specify the bridger implementation to use for each chain.
function init(
uint256[] memory _chainIds,
IDaimoPayBridger[] memory _bridgers
) public onlyOwner {
uint256 n = _chainIds.length;
require(n == _bridgers.length, "DPB: wrong bridgers length");

for (uint256 i = 0; i < n; ++i) {
_addBridger(_chainIds[i], _bridgers[i]);
}
}

/// Add a new bridger for a destination chain.
function addBridger(
uint256 chainId,
IDaimoPayBridger bridger
) public onlyOwner {
_addBridger(chainId, bridger);
}

function _addBridger(uint256 chainId, IDaimoPayBridger bridger) private {
require(chainId != 0, "DPB: missing chainId");
bridgerMapping[chainId] = bridger;
emit BridgeAdded(chainId, address(bridger));
}

// ----- PUBLIC FUNCTIONS -----

/// Initiate a bridge to a supported destination chain.
function sendToChain(
uint256 toChainId,
address toAddress,
address toToken,
uint256 toAmount,
bytes calldata extraData
) public {
require(toAmount > 0, "DPB: zero amount");

if (toChainId == block.chainid) {
// Same chain. Transfer token to toAddress.
TokenUtils.transfer(IERC20(toToken), payable(toAddress), toAmount);
} else {
// Different chains. Bridge (via CCTP, etc)
IDaimoPayBridger bridger = bridgerMapping[toChainId];
require(address(bridger) != address(0), "DPB: missing bridger");

bridger.sendToChain(
toChainId,
toAddress,
toToken,
toAmount,
extraData
);
}
}
}
Loading