Set up CrossChainUSDC contracts and implemenet transferTokens method
This commit is contained in:
parent
45c11abf85
commit
c697a27255
|
@ -1,113 +0,0 @@
|
||||||
// SPDX-License-Identifier: UNLICENSED
|
|
||||||
pragma solidity ^0.8.13;
|
|
||||||
|
|
||||||
import {IWormhole} from "../interfaces/IWormhole.sol";
|
|
||||||
import "../libraries/BytesLib.sol";
|
|
||||||
|
|
||||||
import "./HelloWorldGetters.sol";
|
|
||||||
import "./HelloWorldMessages.sol";
|
|
||||||
|
|
||||||
contract HelloWorld is HelloWorldGetters, HelloWorldMessages {
|
|
||||||
using BytesLib for bytes;
|
|
||||||
|
|
||||||
constructor(address wormhole_, uint16 chainId_, uint8 wormholeFinality_) {
|
|
||||||
// sanity check input values
|
|
||||||
require(wormhole_ != address(0), "invalid wormhole address");
|
|
||||||
require(chainId_ > 0, "invalid chainId");
|
|
||||||
require(wormholeFinality_ > 0, "invalid wormholeFinality");
|
|
||||||
|
|
||||||
// set constructor state values
|
|
||||||
setOwner(msg.sender);
|
|
||||||
setWormhole(wormhole_);
|
|
||||||
setChainId(chainId_);
|
|
||||||
setWormholeFinality(wormholeFinality_);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendMessage() public payable returns (uint64 messageSequence) {
|
|
||||||
// cache wormhole instance and fees to save on gas
|
|
||||||
IWormhole wormhole = wormhole();
|
|
||||||
uint256 wormholeFee = wormhole.messageFee();
|
|
||||||
|
|
||||||
// Confirm that the caller has sent enough ether to pay for the wormhole
|
|
||||||
// message fee.
|
|
||||||
require(msg.value == wormholeFee, "insufficient value");
|
|
||||||
|
|
||||||
// create the HelloWorldMessage struct
|
|
||||||
HelloWorldMessage memory parsedMessage = HelloWorldMessage({
|
|
||||||
payloadID: uint8(1),
|
|
||||||
message: "HelloSolana"
|
|
||||||
});
|
|
||||||
|
|
||||||
// encode the message
|
|
||||||
bytes memory encodedMessage = encodeMessage(parsedMessage);
|
|
||||||
|
|
||||||
// Send the HelloWorld message by calling publishMessage on the
|
|
||||||
// wormhole core contract.
|
|
||||||
messageSequence = wormhole.publishMessage{value: wormholeFee}(
|
|
||||||
42000, // user specified message ID
|
|
||||||
encodedMessage,
|
|
||||||
wormholeFinality()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function receiveMessage(bytes memory encodedMessage) public {
|
|
||||||
// call the wormhole core contract to parse and verify the encodedMessage
|
|
||||||
(
|
|
||||||
IWormhole.VM memory wormholeMessage,
|
|
||||||
bool valid,
|
|
||||||
string memory reason
|
|
||||||
) = wormhole().parseAndVerifyVM(encodedMessage);
|
|
||||||
|
|
||||||
// confirm that the core layer verified the message
|
|
||||||
require(valid, reason);
|
|
||||||
|
|
||||||
// verify that this message was emitted by a trusted contract
|
|
||||||
require(verifyEmitter(wormholeMessage), "unknown emitter");
|
|
||||||
|
|
||||||
// decode the message payload into the HelloWorldStruct
|
|
||||||
HelloWorldMessage memory parsedMessage = decodeMessage(wormholeMessage.payload);
|
|
||||||
|
|
||||||
/**
|
|
||||||
Check to see if this message has been consumed already. If not,
|
|
||||||
save the parsed message in the receivedMessages mapping.
|
|
||||||
|
|
||||||
This check can protect against replay attacks in xDapps where messages are
|
|
||||||
only meant to be consumed once.
|
|
||||||
*/
|
|
||||||
require(!isMessageConsumed(wormholeMessage.hash), "message already consumed");
|
|
||||||
consumeMessage(wormholeMessage.hash, parsedMessage.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerEmitter(
|
|
||||||
uint16 emitterChainId,
|
|
||||||
bytes32 emitterAddress
|
|
||||||
) public onlyOwner {
|
|
||||||
// sanity check both input arguments
|
|
||||||
require(
|
|
||||||
emitterAddress != bytes32(0),
|
|
||||||
"emitterAddress cannot equal bytes32(0)"
|
|
||||||
);
|
|
||||||
require(
|
|
||||||
getRegisteredEmitter(emitterChainId) == bytes32(0),
|
|
||||||
"emitterChainId already registered"
|
|
||||||
);
|
|
||||||
|
|
||||||
// update the registeredEmitters state variable
|
|
||||||
setEmitter(emitterChainId, emitterAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
function verifyEmitter(IWormhole.VM memory vm) internal view returns (bool) {
|
|
||||||
// Verify that the sender of the wormhole message is a trusted
|
|
||||||
// HelloWorld contract.
|
|
||||||
if (getRegisteredEmitter(vm.emitterChainId) == vm.emitterAddress) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
modifier onlyOwner() {
|
|
||||||
require(owner() == msg.sender, "caller not the owner");
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
// SPDX-License-Identifier: UNLICENSED
|
|
||||||
pragma solidity ^0.8.13;
|
|
||||||
|
|
||||||
import "../libraries/BytesLib.sol";
|
|
||||||
|
|
||||||
import "./HelloWorldStructs.sol";
|
|
||||||
|
|
||||||
contract HelloWorldMessages is HelloWorldStructs {
|
|
||||||
using BytesLib for bytes;
|
|
||||||
|
|
||||||
function encodeMessage(
|
|
||||||
HelloWorldMessage memory parsedMessage
|
|
||||||
) public pure returns (bytes memory) {
|
|
||||||
// convert message string to bytes so that we can use the .length attribute
|
|
||||||
bytes memory encodedMessagePayload = abi.encodePacked(parsedMessage.message);
|
|
||||||
|
|
||||||
// return the encoded message
|
|
||||||
return abi.encodePacked(
|
|
||||||
parsedMessage.payloadID,
|
|
||||||
encodedMessagePayload.length,
|
|
||||||
encodedMessagePayload
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function decodeMessage(
|
|
||||||
bytes memory encodedMessage
|
|
||||||
) public pure returns (HelloWorldMessage memory parsedMessage) {
|
|
||||||
// starting index for byte parsing
|
|
||||||
uint256 index = 0;
|
|
||||||
|
|
||||||
// parse and verify the payloadID
|
|
||||||
parsedMessage.payloadID = encodedMessage.toUint8(index);
|
|
||||||
require(parsedMessage.payloadID == 1, "invalid payloadID");
|
|
||||||
index += 1;
|
|
||||||
|
|
||||||
// parse the message string length
|
|
||||||
uint256 messageLength = encodedMessage.toUint256(index);
|
|
||||||
index += 32;
|
|
||||||
|
|
||||||
// parse the message string
|
|
||||||
bytes memory messageBytes = encodedMessage.slice(index, messageLength);
|
|
||||||
parsedMessage.message = string(messageBytes);
|
|
||||||
index += messageLength;
|
|
||||||
|
|
||||||
// confirm that the message was the expected length
|
|
||||||
require(index == encodedMessage.length, "invalid message length");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
// SPDX-License-Identifier: UNLICENSED
|
|
||||||
pragma solidity ^0.8.13;
|
|
||||||
|
|
||||||
import "./HelloWorldState.sol";
|
|
||||||
|
|
||||||
contract HelloWorldSetters is HelloWorldState {
|
|
||||||
function setOwner(address owner_) internal {
|
|
||||||
_state.owner = owner_;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setWormhole(address wormhole_) internal {
|
|
||||||
_state.wormhole = payable(wormhole_);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setChainId(uint16 chainId_) internal {
|
|
||||||
_state.chainId = chainId_;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setWormholeFinality(uint8 finality) internal {
|
|
||||||
_state.wormholeFinality = finality;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setEmitter(uint16 chainId, bytes32 emitter) internal {
|
|
||||||
_state.registeredEmitters[chainId] = emitter;
|
|
||||||
}
|
|
||||||
|
|
||||||
function consumeMessage(bytes32 hash, string memory message) internal {
|
|
||||||
_state.receivedMessages[hash] = message;
|
|
||||||
_state.consumedMessages[hash] = true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
// SPDX-License-Identifier: UNLICENSED
|
|
||||||
pragma solidity ^0.8.13;
|
|
||||||
|
|
||||||
import {IWormhole} from "../interfaces/IWormhole.sol";
|
|
||||||
|
|
||||||
contract HelloWorldStorage {
|
|
||||||
struct State {
|
|
||||||
// owner of this contract
|
|
||||||
address owner;
|
|
||||||
|
|
||||||
// address of the Wormhole contract on this chain
|
|
||||||
address wormhole;
|
|
||||||
|
|
||||||
// Wormhole chain ID of this contract
|
|
||||||
uint16 chainId;
|
|
||||||
|
|
||||||
// The number of block confirmations needed before the wormhole network
|
|
||||||
// will attest a message.
|
|
||||||
uint8 wormholeFinality;
|
|
||||||
|
|
||||||
// Wormhole chain ID to known emitter address mapping. Xapps using
|
|
||||||
// Wormhole should register all deployed contracts on each chain to
|
|
||||||
// verify that messages being consumed are from trusted contracts.
|
|
||||||
mapping(uint16 => bytes32) registeredEmitters;
|
|
||||||
|
|
||||||
// verified message hash to received message mapping
|
|
||||||
mapping(bytes32 => string) receivedMessages;
|
|
||||||
|
|
||||||
// verified message hash to boolean
|
|
||||||
mapping(bytes32 => bool) consumedMessages;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
contract HelloWorldState {
|
|
||||||
HelloWorldStorage.State _state;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
// SPDX-License-Identifier: UNLICENSED
|
|
||||||
pragma solidity ^0.8.13;
|
|
||||||
|
|
||||||
contract HelloWorldStructs {
|
|
||||||
struct HelloWorldMessage {
|
|
||||||
// unique identifier
|
|
||||||
uint8 payloadID;
|
|
||||||
// message payload (max size uint256)
|
|
||||||
string message;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
pragma solidity ^0.8.13;
|
||||||
|
|
||||||
|
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
||||||
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
|
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||||
|
import "../libraries/BytesLib.sol";
|
||||||
|
|
||||||
|
import {IWormhole} from "../interfaces/IWormhole.sol";
|
||||||
|
|
||||||
|
import "./CrossChainUSDCGovernance.sol";
|
||||||
|
import "./CrossChainUSDCMessages.sol";
|
||||||
|
|
||||||
|
contract CrossChainUSDC is CrossChainUSDCMessages, CrossChainUSDCGovernance, ReentrancyGuard {
|
||||||
|
using BytesLib for bytes;
|
||||||
|
|
||||||
|
function transferTokens(
|
||||||
|
address token,
|
||||||
|
uint256 amount,
|
||||||
|
uint16 toChain,
|
||||||
|
bytes32 mintRecipient
|
||||||
|
) public payable nonReentrant returns (uint64 messageSequence) {
|
||||||
|
// sanity check user input
|
||||||
|
require(amount > 0, "amount must be > 0");
|
||||||
|
require(toChain > 0, "invalid to chainId");
|
||||||
|
require(mintRecipient != bytes32(0), "invalid mint recipient");
|
||||||
|
|
||||||
|
// take custody of tokens
|
||||||
|
_custodyTokens(token, amount);
|
||||||
|
|
||||||
|
// cache wormhole instance and fees to save on gas
|
||||||
|
IWormhole wormhole = wormhole();
|
||||||
|
uint256 wormholeFee = wormhole.messageFee();
|
||||||
|
|
||||||
|
// Confirm that the caller has sent enough ether to pay for the wormhole
|
||||||
|
// message fee.
|
||||||
|
require(msg.value == wormholeFee, "insufficient value");
|
||||||
|
|
||||||
|
// cache Circle Bridge instance
|
||||||
|
ICircleBridge circleBridge = circleBridge();
|
||||||
|
|
||||||
|
// approve the USDC Bridge to spend tokens
|
||||||
|
SafeERC20.safeApprove(
|
||||||
|
IERC20(token),
|
||||||
|
address(circleBridge),
|
||||||
|
amount
|
||||||
|
);
|
||||||
|
|
||||||
|
// cache toChain information to save gas
|
||||||
|
uint32 targetChainDomain = getChainDomain(toChain);
|
||||||
|
bytes32 targetContract = getRegisteredEmitter(toChain);
|
||||||
|
|
||||||
|
// burn USDC on the bridge
|
||||||
|
uint64 nonce = circleBridge.depositForBurnWithCaller(
|
||||||
|
amount,
|
||||||
|
targetChainDomain,
|
||||||
|
mintRecipient,
|
||||||
|
token,
|
||||||
|
targetContract
|
||||||
|
);
|
||||||
|
|
||||||
|
// encode depositForBurn message
|
||||||
|
bytes memory encodedMessage = encodeDepositForBurnMessage(
|
||||||
|
DepositForBurn({
|
||||||
|
payloadId: uint8(1),
|
||||||
|
token: addressToBytes32(token),
|
||||||
|
amount: amount,
|
||||||
|
sourceDomain: getChainDomain(chainId()),
|
||||||
|
targetDomain: targetChainDomain,
|
||||||
|
nonce: nonce,
|
||||||
|
sender: addressToBytes32(address(this)),
|
||||||
|
mintRecipient: mintRecipient
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// send the DepositForBurn wormhole message
|
||||||
|
messageSequence = wormhole.publishMessage{value : wormholeFee}(
|
||||||
|
0, // messageId, set to zero to opt out of batching
|
||||||
|
encodedMessage,
|
||||||
|
wormholeFinality()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _custodyTokens(address token, uint256 amount) internal {
|
||||||
|
/// query own token balance before transfer
|
||||||
|
(,bytes memory queriedBalanceBefore) = token.staticcall(
|
||||||
|
abi.encodeWithSelector(IERC20.balanceOf.selector,
|
||||||
|
address(this))
|
||||||
|
);
|
||||||
|
uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256));
|
||||||
|
|
||||||
|
/// deposit USDC/EUROC
|
||||||
|
SafeERC20.safeTransferFrom(
|
||||||
|
IERC20(token),
|
||||||
|
msg.sender,
|
||||||
|
address(this),
|
||||||
|
amount
|
||||||
|
);
|
||||||
|
|
||||||
|
/// query own token balance after transfer
|
||||||
|
(,bytes memory queriedBalanceAfter) = token.staticcall(
|
||||||
|
abi.encodeWithSelector(IERC20.balanceOf.selector,
|
||||||
|
address(this))
|
||||||
|
);
|
||||||
|
uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256));
|
||||||
|
|
||||||
|
// This check is necessary until circle publishes the source code
|
||||||
|
// for the USDC Bridge.
|
||||||
|
require(
|
||||||
|
amount == balanceAfter - balanceBefore,
|
||||||
|
"USDC doesn't charge fees :/"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyEmitter(IWormhole.VM memory vm) internal view returns (bool) {
|
||||||
|
// verify that the sender of the wormhole message is a trusted
|
||||||
|
if (getRegisteredEmitter(vm.emitterChainId) == vm.emitterAddress) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addressToBytes32(address address_) public pure returns (bytes32) {
|
||||||
|
return bytes32(uint256(uint160(address_)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,24 @@
|
||||||
// SPDX-License-Identifier: UNLICENSED
|
// SPDX-License-Identifier: Apache 2
|
||||||
pragma solidity ^0.8.13;
|
pragma solidity ^0.8.13;
|
||||||
|
|
||||||
import {IWormhole} from "../interfaces/IWormhole.sol";
|
import {IWormhole} from "../interfaces/IWormhole.sol";
|
||||||
|
import {ICircleBridge} from "../interfaces/circle/ICircleBridge.sol";
|
||||||
|
|
||||||
import "./HelloWorldSetters.sol";
|
import "./CrossChainUSDCSetters.sol";
|
||||||
|
|
||||||
contract HelloWorldGetters is HelloWorldSetters {
|
contract CrossChainUSDCGetters is CrossChainUSDCSetters {
|
||||||
function owner() public view returns (address) {
|
function owner() public view returns (address) {
|
||||||
return _state.owner;
|
return _state.owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pendingOwner() public view returns (address) {
|
||||||
|
return _state.pendingOwner;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInitialized(address impl) public view returns (bool) {
|
||||||
|
return _state.initializedImplementations[impl];
|
||||||
|
}
|
||||||
|
|
||||||
function wormhole() public view returns (IWormhole) {
|
function wormhole() public view returns (IWormhole) {
|
||||||
return IWormhole(_state.wormhole);
|
return IWormhole(_state.wormhole);
|
||||||
}
|
}
|
||||||
|
@ -22,12 +31,16 @@ contract HelloWorldGetters is HelloWorldSetters {
|
||||||
return _state.wormholeFinality;
|
return _state.wormholeFinality;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function circleBridge() public view returns (ICircleBridge) {
|
||||||
|
return ICircleBridge(_state.circleBridgeAddress);
|
||||||
|
}
|
||||||
|
|
||||||
function getRegisteredEmitter(uint16 emitterChainId) public view returns (bytes32) {
|
function getRegisteredEmitter(uint16 emitterChainId) public view returns (bytes32) {
|
||||||
return _state.registeredEmitters[emitterChainId];
|
return _state.registeredEmitters[emitterChainId];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getReceivedMessage(bytes32 hash) public view returns (string memory) {
|
function getChainDomain(uint16 chainId_) public view returns (uint32) {
|
||||||
return _state.receivedMessages[hash];
|
return _state.chainDomains[chainId_];
|
||||||
}
|
}
|
||||||
|
|
||||||
function isMessageConsumed(bytes32 hash) public view returns (bool) {
|
function isMessageConsumed(bytes32 hash) public view returns (bool) {
|
|
@ -0,0 +1,112 @@
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
pragma solidity ^0.8.13;
|
||||||
|
|
||||||
|
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol";
|
||||||
|
|
||||||
|
import "./CrossChainUSDCSetters.sol";
|
||||||
|
import "./CrossChainUSDCGetters.sol";
|
||||||
|
import "./CrossChainUSDCState.sol";
|
||||||
|
|
||||||
|
contract CrossChainUSDCGovernance is CrossChainUSDCGetters, ERC1967Upgrade {
|
||||||
|
event ContractUpgraded(address indexed oldContract, address indexed newContract);
|
||||||
|
event WormholeFinalityUpdated(uint8 indexed oldLevel, uint8 indexed newFinality);
|
||||||
|
event OwnershipTransfered(address indexed oldOwner, address indexed newOwner);
|
||||||
|
|
||||||
|
/// @dev upgrade serves to upgrade contract implementations
|
||||||
|
function upgrade(uint16 chainId_, address newImplementation) public onlyOwner {
|
||||||
|
require(chainId_ == chainId(), "wrong chain");
|
||||||
|
|
||||||
|
address currentImplementation = _getImplementation();
|
||||||
|
|
||||||
|
_upgradeTo(newImplementation);
|
||||||
|
|
||||||
|
/// @dev call initialize function of the new implementation
|
||||||
|
(bool success, bytes memory reason) = newImplementation.delegatecall(
|
||||||
|
abi.encodeWithSignature("initialize()")
|
||||||
|
);
|
||||||
|
|
||||||
|
require(success, string(reason));
|
||||||
|
|
||||||
|
emit ContractUpgraded(currentImplementation, newImplementation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev updateWormholeFinality serves to change the wormhole messaging consistencyLevel
|
||||||
|
function updateWormholeFinality(
|
||||||
|
uint16 chainId_,
|
||||||
|
uint8 newWormholeFinality
|
||||||
|
) public onlyOwner {
|
||||||
|
require(chainId_ == chainId(), "wrong chain");
|
||||||
|
require(newWormholeFinality > 0, "invalid wormhole finality");
|
||||||
|
|
||||||
|
uint8 currentWormholeFinality = wormholeFinality();
|
||||||
|
|
||||||
|
setWormholeFinality(newWormholeFinality);
|
||||||
|
|
||||||
|
emit WormholeFinalityUpdated(currentWormholeFinality, newWormholeFinality);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev submitOwnershipTransferRequest serves to begin the ownership transfer process of the contracts
|
||||||
|
* - it saves an address for the new owner in the pending state
|
||||||
|
*/
|
||||||
|
function submitOwnershipTransferRequest(
|
||||||
|
uint16 chainId_,
|
||||||
|
address newOwner
|
||||||
|
) public onlyOwner {
|
||||||
|
require(chainId_ == chainId(), "wrong chain");
|
||||||
|
require(newOwner != address(0), "newOwner cannot equal address(0)");
|
||||||
|
|
||||||
|
setPendingOwner(newOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev confirmOwnershipTransferRequest serves to finalize an ownership transfer
|
||||||
|
* - it checks that the caller is the pendingOwner to validate the wallet address
|
||||||
|
* - it updates the owner state variable with the pendingOwner state variable
|
||||||
|
*/
|
||||||
|
function confirmOwnershipTransferRequest() public {
|
||||||
|
/// cache the new owner address
|
||||||
|
address newOwner = pendingOwner();
|
||||||
|
|
||||||
|
require(msg.sender == newOwner, "caller must be pendingOwner");
|
||||||
|
|
||||||
|
/// cache currentOwner for Event
|
||||||
|
address currentOwner = owner();
|
||||||
|
|
||||||
|
/// @dev update the owner in the contract state and reset the pending owner
|
||||||
|
setOwner(newOwner);
|
||||||
|
setPendingOwner(address(0));
|
||||||
|
|
||||||
|
emit OwnershipTransfered(currentOwner, newOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev registerEmitter serves to save trusted emitter contract addresses
|
||||||
|
function registerEmitter(
|
||||||
|
uint16 emitterChainId,
|
||||||
|
bytes32 emitterAddress
|
||||||
|
) public onlyOwner {
|
||||||
|
// sanity check both input arguments
|
||||||
|
require(
|
||||||
|
emitterAddress != bytes32(0),
|
||||||
|
"emitterAddress cannot equal bytes32(0)"
|
||||||
|
);
|
||||||
|
require(
|
||||||
|
getRegisteredEmitter(emitterChainId) == bytes32(0),
|
||||||
|
"emitterChainId already registered"
|
||||||
|
);
|
||||||
|
|
||||||
|
// update the registeredEmitters state variable
|
||||||
|
setEmitter(emitterChainId, emitterAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev registerChainDomain serves to save the USDC Bridge chain domains
|
||||||
|
function registerChainDomain(uint16 chainId_, uint32 domain) public onlyOwner {
|
||||||
|
// update the chainDomains state variable
|
||||||
|
setChainDomain(chainId_, domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier onlyOwner() {
|
||||||
|
require(owner() == msg.sender, "caller not the owner");
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
pragma solidity ^0.8.13;
|
||||||
|
|
||||||
|
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol";
|
||||||
|
|
||||||
|
import "./CrossChainUSDC.sol";
|
||||||
|
|
||||||
|
contract CrossChainUSDCImplementation is CrossChainUSDC {
|
||||||
|
function initialize() initializer public virtual {
|
||||||
|
// this function needs to be exposed for an upgrade to pass
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier initializer() {
|
||||||
|
address impl = ERC1967Upgrade._getImplementation();
|
||||||
|
|
||||||
|
require(
|
||||||
|
!isInitialized(impl),
|
||||||
|
"already initialized"
|
||||||
|
);
|
||||||
|
|
||||||
|
setInitialized(impl);
|
||||||
|
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
pragma solidity ^0.8.13;
|
||||||
|
|
||||||
|
import "../libraries/BytesLib.sol";
|
||||||
|
|
||||||
|
import "./CrossChainUSDCStructs.sol";
|
||||||
|
|
||||||
|
contract CrossChainUSDCMessages is CrossChainUSDCStructs {
|
||||||
|
using BytesLib for bytes;
|
||||||
|
|
||||||
|
function encodeDepositForBurnMessage(
|
||||||
|
DepositForBurn memory message
|
||||||
|
) public pure returns (bytes memory) {
|
||||||
|
return abi.encodePacked(
|
||||||
|
uint8(1), // payloadId
|
||||||
|
message.token,
|
||||||
|
message.amount,
|
||||||
|
message.sourceDomain,
|
||||||
|
message.targetDomain,
|
||||||
|
message.nonce,
|
||||||
|
message.sender,
|
||||||
|
message.mintRecipient
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeDepositForBurnMessage(
|
||||||
|
bytes memory encoded
|
||||||
|
) public pure returns (DepositForBurn memory message) {
|
||||||
|
uint256 index = 0;
|
||||||
|
|
||||||
|
// payloadId
|
||||||
|
message.payloadId = encoded.toUint8(index);
|
||||||
|
index += 1;
|
||||||
|
|
||||||
|
require(message.payloadId == 1, "invalid message payloadId");
|
||||||
|
|
||||||
|
// token address, encoded as bytes32
|
||||||
|
message.token = encoded.toBytes32(index);
|
||||||
|
index += 32;
|
||||||
|
|
||||||
|
// amount burned
|
||||||
|
message.amount = encoded.toUint256(index);
|
||||||
|
index += 32;
|
||||||
|
|
||||||
|
// source domain
|
||||||
|
message.sourceDomain = encoded.toUint32(index);
|
||||||
|
index += 4;
|
||||||
|
|
||||||
|
// target domain
|
||||||
|
message.targetDomain = encoded.toUint32(index);
|
||||||
|
index += 4;
|
||||||
|
|
||||||
|
// nonce
|
||||||
|
message.nonce = encoded.toUint64(index);
|
||||||
|
index += 8;
|
||||||
|
|
||||||
|
// message sender
|
||||||
|
message.sender = encoded.toBytes32(index);
|
||||||
|
index += 32;
|
||||||
|
|
||||||
|
// mint recipient
|
||||||
|
message.mintRecipient = encoded.toBytes32(index);
|
||||||
|
index += 32;
|
||||||
|
|
||||||
|
require(index == encoded.length, "invalid message length");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
pragma solidity ^0.8.13;
|
||||||
|
|
||||||
|
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
||||||
|
|
||||||
|
contract CrossChainUSDCProxy is ERC1967Proxy {
|
||||||
|
constructor (address implementation, bytes memory initData)
|
||||||
|
ERC1967Proxy(
|
||||||
|
implementation,
|
||||||
|
initData
|
||||||
|
)
|
||||||
|
{}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
pragma solidity ^0.8.13;
|
||||||
|
|
||||||
|
import "./CrossChainUSDCState.sol";
|
||||||
|
|
||||||
|
contract CrossChainUSDCSetters is CrossChainUSDCState {
|
||||||
|
function setOwner(address owner_) internal {
|
||||||
|
_state.owner = owner_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPendingOwner(address pendingOwner_) internal {
|
||||||
|
_state.pendingOwner = pendingOwner_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInitialized(address implementatiom) internal {
|
||||||
|
_state.initializedImplementations[implementatiom] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWormhole(address wormhole_) internal {
|
||||||
|
_state.wormhole = payable(wormhole_);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setChainId(uint16 chainId_) internal {
|
||||||
|
_state.chainId = chainId_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWormholeFinality(uint8 finality) internal {
|
||||||
|
_state.wormholeFinality = finality;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCircleBridge(address circleBridgeAddress_) internal {
|
||||||
|
_state.circleBridgeAddress = circleBridgeAddress_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setEmitter(uint16 chainId_, bytes32 emitter) internal {
|
||||||
|
_state.registeredEmitters[chainId_] = emitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setChainDomain(uint16 chainId_, uint32 domain) internal {
|
||||||
|
_state.chainDomains[chainId_] = domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
function consumeMessage(bytes32 hash) internal {
|
||||||
|
_state.consumedMessages[hash] = true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
pragma solidity ^0.8.13;
|
||||||
|
|
||||||
|
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol";
|
||||||
|
import {Context} from "@openzeppelin/contracts/utils/Context.sol";
|
||||||
|
|
||||||
|
import "./CrossChainUSDCSetters.sol";
|
||||||
|
|
||||||
|
contract CrossChainUSDCSetup is CrossChainUSDCSetters, ERC1967Upgrade, Context {
|
||||||
|
function setup(
|
||||||
|
address implementation,
|
||||||
|
uint16 chainId,
|
||||||
|
address wormhole,
|
||||||
|
uint8 finality,
|
||||||
|
address circleBridgeAddress
|
||||||
|
) public {
|
||||||
|
require(implementation != address(0), "invalid implementation");
|
||||||
|
require(chainId > 0, "invalid chainId");
|
||||||
|
require(wormhole != address(0), "invalid wormhole address");
|
||||||
|
require(circleBridgeAddress != address(0), "invalid USDC Bridge address");
|
||||||
|
|
||||||
|
setOwner(_msgSender());
|
||||||
|
setChainId(chainId);
|
||||||
|
setWormhole(wormhole);
|
||||||
|
setWormholeFinality(finality);
|
||||||
|
setCircleBridge(circleBridgeAddress);
|
||||||
|
|
||||||
|
// set the implementation
|
||||||
|
_upgradeTo(implementation);
|
||||||
|
|
||||||
|
// call initialize function of the new implementation
|
||||||
|
(bool success, bytes memory reason) = implementation.delegatecall(abi.encodeWithSignature("initialize()"));
|
||||||
|
require(success, string(reason));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
pragma solidity ^0.8.13;
|
||||||
|
|
||||||
|
import {IWormhole} from "../interfaces/IWormhole.sol";
|
||||||
|
|
||||||
|
contract CrossChainUSDCStorage {
|
||||||
|
struct State {
|
||||||
|
// Wormhole chain ID of this contract
|
||||||
|
uint16 chainId;
|
||||||
|
|
||||||
|
// The number of block confirmations needed before the wormhole network
|
||||||
|
// will attest a message.
|
||||||
|
uint8 wormholeFinality;
|
||||||
|
|
||||||
|
// owner of this contract
|
||||||
|
address owner;
|
||||||
|
|
||||||
|
// intermediate state when transfering contract ownership
|
||||||
|
address pendingOwner;
|
||||||
|
|
||||||
|
// address of the Wormhole contract on this chain
|
||||||
|
address wormhole;
|
||||||
|
|
||||||
|
// address of the trusted Circle Bridge contract on this chain
|
||||||
|
address circleBridgeAddress;
|
||||||
|
|
||||||
|
// mapping of initialized implementations
|
||||||
|
mapping(address => bool) initializedImplementations;
|
||||||
|
|
||||||
|
// Wormhole chain ID to known emitter address mapping
|
||||||
|
mapping(uint16 => bytes32) registeredEmitters;
|
||||||
|
|
||||||
|
// Wormhole chain ID to USDC Chain Domain Mapping
|
||||||
|
mapping(uint16 => uint32) chainDomains;
|
||||||
|
|
||||||
|
// verified message hash to boolean
|
||||||
|
mapping(bytes32 => bool) consumedMessages;
|
||||||
|
|
||||||
|
// storage gap
|
||||||
|
uint256[50] ______gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract CrossChainUSDCState {
|
||||||
|
CrossChainUSDCStorage.State _state;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
pragma solidity ^0.8.13;
|
||||||
|
|
||||||
|
contract CrossChainUSDCStructs {
|
||||||
|
struct DepositForBurn {
|
||||||
|
uint8 payloadId; // == 1
|
||||||
|
bytes32 token;
|
||||||
|
uint256 amount;
|
||||||
|
uint32 sourceDomain;
|
||||||
|
uint32 targetDomain;
|
||||||
|
uint64 nonce;
|
||||||
|
bytes32 sender; // this contract
|
||||||
|
bytes32 mintRecipient;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue