diff --git a/evm/src/01_hello_world/HelloWorld.sol b/evm/src/01_hello_world/HelloWorld.sol deleted file mode 100644 index c7394e0..0000000 --- a/evm/src/01_hello_world/HelloWorld.sol +++ /dev/null @@ -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"); - _; - } -} diff --git a/evm/src/01_hello_world/HelloWorldMessages.sol b/evm/src/01_hello_world/HelloWorldMessages.sol deleted file mode 100644 index c0da6a7..0000000 --- a/evm/src/01_hello_world/HelloWorldMessages.sol +++ /dev/null @@ -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"); - } -} diff --git a/evm/src/01_hello_world/HelloWorldSetters.sol b/evm/src/01_hello_world/HelloWorldSetters.sol deleted file mode 100644 index bac3987..0000000 --- a/evm/src/01_hello_world/HelloWorldSetters.sol +++ /dev/null @@ -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; - } -} diff --git a/evm/src/01_hello_world/HelloWorldState.sol b/evm/src/01_hello_world/HelloWorldState.sol deleted file mode 100644 index 2043378..0000000 --- a/evm/src/01_hello_world/HelloWorldState.sol +++ /dev/null @@ -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; -} - diff --git a/evm/src/01_hello_world/HelloWorldStructs.sol b/evm/src/01_hello_world/HelloWorldStructs.sol deleted file mode 100644 index dfa3bed..0000000 --- a/evm/src/01_hello_world/HelloWorldStructs.sol +++ /dev/null @@ -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; - } -} diff --git a/evm/src/cross_chain_usdc/CrossChainUSDC.sol b/evm/src/cross_chain_usdc/CrossChainUSDC.sol new file mode 100644 index 0000000..2beae6e --- /dev/null +++ b/evm/src/cross_chain_usdc/CrossChainUSDC.sol @@ -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_))); + } +} diff --git a/evm/src/01_hello_world/HelloWorldGetters.sol b/evm/src/cross_chain_usdc/CrossChainUSDCGetters.sol similarity index 52% rename from evm/src/01_hello_world/HelloWorldGetters.sol rename to evm/src/cross_chain_usdc/CrossChainUSDCGetters.sol index f0c9b88..1e3152e 100644 --- a/evm/src/01_hello_world/HelloWorldGetters.sol +++ b/evm/src/cross_chain_usdc/CrossChainUSDCGetters.sol @@ -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) { diff --git a/evm/src/cross_chain_usdc/CrossChainUSDCGovernance.sol b/evm/src/cross_chain_usdc/CrossChainUSDCGovernance.sol new file mode 100644 index 0000000..3799bb1 --- /dev/null +++ b/evm/src/cross_chain_usdc/CrossChainUSDCGovernance.sol @@ -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"); + _; + } +} \ No newline at end of file diff --git a/evm/src/cross_chain_usdc/CrossChainUSDCImplementation.sol b/evm/src/cross_chain_usdc/CrossChainUSDCImplementation.sol new file mode 100644 index 0000000..b226178 --- /dev/null +++ b/evm/src/cross_chain_usdc/CrossChainUSDCImplementation.sol @@ -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); + + _; + } +} \ No newline at end of file diff --git a/evm/src/cross_chain_usdc/CrossChainUSDCMessages.sol b/evm/src/cross_chain_usdc/CrossChainUSDCMessages.sol new file mode 100644 index 0000000..39868f7 --- /dev/null +++ b/evm/src/cross_chain_usdc/CrossChainUSDCMessages.sol @@ -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"); + } +} diff --git a/evm/src/cross_chain_usdc/CrossChainUSDCProxy.sol b/evm/src/cross_chain_usdc/CrossChainUSDCProxy.sol new file mode 100644 index 0000000..654e1f6 --- /dev/null +++ b/evm/src/cross_chain_usdc/CrossChainUSDCProxy.sol @@ -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 + ) + {} +} \ No newline at end of file diff --git a/evm/src/cross_chain_usdc/CrossChainUSDCSetters.sol b/evm/src/cross_chain_usdc/CrossChainUSDCSetters.sol new file mode 100644 index 0000000..58e86b2 --- /dev/null +++ b/evm/src/cross_chain_usdc/CrossChainUSDCSetters.sol @@ -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; + } +} diff --git a/evm/src/cross_chain_usdc/CrossChainUSDCSetup.sol b/evm/src/cross_chain_usdc/CrossChainUSDCSetup.sol new file mode 100644 index 0000000..586c829 --- /dev/null +++ b/evm/src/cross_chain_usdc/CrossChainUSDCSetup.sol @@ -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)); + } +} \ No newline at end of file diff --git a/evm/src/cross_chain_usdc/CrossChainUSDCState.sol b/evm/src/cross_chain_usdc/CrossChainUSDCState.sol new file mode 100644 index 0000000..b1ccf2d --- /dev/null +++ b/evm/src/cross_chain_usdc/CrossChainUSDCState.sol @@ -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; +} + diff --git a/evm/src/cross_chain_usdc/CrossChainUSDCStructs.sol b/evm/src/cross_chain_usdc/CrossChainUSDCStructs.sol new file mode 100644 index 0000000..8be3b87 --- /dev/null +++ b/evm/src/cross_chain_usdc/CrossChainUSDCStructs.sol @@ -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; + } +}