trustless-generic-relayer/ethereum/contracts/example_integrations/xMint/Hub.sol

134 lines
5.5 KiB
Solidity

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
//import "solidity-bytes-utils/BytesLib.sol";
import "../../interfaces/IWormhole.sol";
import "../../interfaces/ITokenBridge.sol";
import "../../interfaces/IWormholeReceiver.sol";
import "../../interfaces/IWormholeRelayer.sol";
contract XmintHub is ERC20, IWormholeReceiver {
//using BytesLib for bytes;
mapping(uint16 => bytes32) trustedContracts;
mapping(bytes32 => bool) consumedMessages;
address owner;
IWormhole core_bridge;
ITokenBridge token_bridge;
IWormholeRelayer core_relayer;
uint32 nonce = 1;
uint8 consistencyLevel = 200;
uint32 SAFE_DELIVERY_GAS_CAPTURE = 5000000; //Capture 500k gas for fees
event Log(string indexed str);
constructor(
string memory name_,
string memory symbol_,
address coreBridgeAddress,
address tokenBridgeAddress,
address coreRelayerAddress
) ERC20(name_, symbol_) {
owner = msg.sender;
core_bridge = IWormhole(coreBridgeAddress);
token_bridge = ITokenBridge(tokenBridgeAddress);
core_relayer = IWormholeRelayer(coreRelayerAddress);
}
/**
* This function is used to add spoke contract deployments into the trusted addresses of this
* contract.
*/
function registerApplicationContracts(uint16 chainId, bytes32 emitterAddr) public {
require(msg.sender == owner, "Only owner can register new chains!");
trustedContracts[chainId] = emitterAddr;
}
//This is the function which receives all messages from the remote contracts.
function receiveWormholeMessages(bytes[] memory vaas, bytes[] memory additionalData) public payable override {
//The first message should be from the token bridge, so attempt to redeem it.
ITokenBridge.TransferWithPayload memory transferResult =
token_bridge.parseTransferWithPayload(token_bridge.completeTransferWithPayload(vaas[0]));
// Ensure this transfer originated from a trusted address!
// The token bridge enforces replay protection however, so no need to enforce it here.
// The chain which this came from is a property of the core bridge, so the chain ID is read from the VAA.
uint16 fromChain = core_bridge.parseVM(vaas[0]).emitterChainId;
//Require that the address these tokens were sent from is the trusted remote contract for that chain.
require(transferResult.fromAddress == trustedContracts[fromChain]);
//Calculate how many tokens to mint for the user
//TODO is tokenAddress the origin address or the local foreign address?
uint256 mintAmount =
calculateMintAmount(transferResult.amount, core_relayer.fromWormholeFormat(transferResult.tokenAddress));
//Mint tokens to this contract
_mint(address(this), mintAmount);
//Bridge the tokens back to the spoke contract, maintaining the intendedRecipient, which is inside the payload.
bridgeTokens(fromChain, transferResult.payload, mintAmount);
//Request delivery from the relayer network
requestForward(fromChain, bytesToBytes32(transferResult.payload, 0));
}
function bridgeTokens(uint16 remoteChain, bytes memory payload, uint256 amount) internal {
(bool success, bytes memory data) = address(token_bridge).call{value: amount + core_bridge.messageFee()}(
//token, amount, receipientChain, recipientAddress, nonce, payload
abi.encodeCall(
ITokenBridge.transferTokensWithPayload,
(address(this), amount, remoteChain, trustedContracts[remoteChain], nonce, payload)
)
);
}
function requestForward(uint16 targetChain, bytes32 intendedRecipient) internal {
uint256 maxTransactionFee =
core_relayer.quoteGas(targetChain, SAFE_DELIVERY_GAS_CAPTURE, core_relayer.getDefaultRelayProvider());
uint256 receiverValue = 0;
IWormholeRelayer.Send memory request = IWormholeRelayer.Send({
targetChain: targetChain,
targetAddress: trustedContracts[targetChain],
refundAddress: intendedRecipient, // All remaining funds will be returned to the user now
maxTransactionFee: maxTransactionFee,
receiverValue: receiverValue, // not needed in this case.
relayParameters: core_relayer.getDefaultRelayParams() //no overrides
});
core_relayer.send{value: maxTransactionFee + receiverValue}(
request, nonce, core_relayer.getDefaultRelayProvider()
);
}
//This function calculates how many tokens should be minted to the end user based on how much
//money they sent to this contract.
function calculateMintAmount(uint256 paymentAmount, address paymentToken) internal returns (uint256 mintAmount) {
//Because this is a toy example, we will mint them 1 token regardless of what token they paid with
// or how much they paid.
return 1 * 10 ^ 18;
}
//This function allows you to purchase tokens from the Hub chain. Because this is all on the Hub chain,
// there's no need for relaying.
function purchaseLocal() internal {
//TODO this
}
function mintLocal() internal {
//TODO this
}
function bytesToBytes32(bytes memory b, uint256 offset) private pure returns (bytes32) {
bytes32 out;
for (uint256 i = 0; i < 32; i++) {
out |= bytes32(b[offset + i] & 0xFF) >> (i * 8);
}
return out;
}
}