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;
|
||||
|
||||
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) {
|
||||
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) {
|
||||
return IWormhole(_state.wormhole);
|
||||
}
|
||||
|
@ -22,12 +31,16 @@ contract HelloWorldGetters is HelloWorldSetters {
|
|||
return _state.wormholeFinality;
|
||||
}
|
||||
|
||||
function circleBridge() public view returns (ICircleBridge) {
|
||||
return ICircleBridge(_state.circleBridgeAddress);
|
||||
}
|
||||
|
||||
function getRegisteredEmitter(uint16 emitterChainId) public view returns (bytes32) {
|
||||
return _state.registeredEmitters[emitterChainId];
|
||||
}
|
||||
|
||||
function getReceivedMessage(bytes32 hash) public view returns (string memory) {
|
||||
return _state.receivedMessages[hash];
|
||||
function getChainDomain(uint16 chainId_) public view returns (uint32) {
|
||||
return _state.chainDomains[chainId_];
|
||||
}
|
||||
|
||||
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