From bececc23b8020f3120dff9bff30fd7cfbae61c23 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 17 Dec 2024 09:38:39 +0100 Subject: [PATCH 1/5] forge install: devtools --- .gitmodules | 3 +++ lib/devtools | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/devtools diff --git a/.gitmodules b/.gitmodules index d84f218..be5aa3e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "lib/ve-beacon"] path = lib/ve-beacon url = https://github.com/timeless-fi/ve-beacon +[submodule "lib/devtools"] + path = lib/devtools + url = https://github.com/LayerZero-Labs/devtools diff --git a/lib/devtools b/lib/devtools new file mode 160000 index 0000000..108f4d6 --- /dev/null +++ b/lib/devtools @@ -0,0 +1 @@ +Subproject commit 108f4d698d7831c00ebffea972123aabd2a231a1 From b5e84572296c07a7c295adb1258029ec1f823bfb Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 17 Dec 2024 09:39:03 +0100 Subject: [PATCH 2/5] forge install: layerzero-v2 --- .gitmodules | 3 +++ lib/layerzero-v2 | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/layerzero-v2 diff --git a/.gitmodules b/.gitmodules index be5aa3e..efa1ba2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "lib/devtools"] path = lib/devtools url = https://github.com/LayerZero-Labs/devtools +[submodule "lib/layerzero-v2"] + path = lib/layerzero-v2 + url = https://github.com/LayerZero-Labs/layerzero-v2 diff --git a/lib/layerzero-v2 b/lib/layerzero-v2 new file mode 160000 index 0000000..34321ac --- /dev/null +++ b/lib/layerzero-v2 @@ -0,0 +1 @@ +Subproject commit 34321ac15e47e0dafd25d66659e2f3d1b9b6db8f From a0c05b8c9a645d85854bf0e89481024be19ccc37 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 17 Dec 2024 16:26:43 +0100 Subject: [PATCH 3/5] Add oVCX bridge adapter and L2 receiver contracts --- foundry.toml | 5 ++ script/DeployOVCX.s.sol | 91 ++++++++++++++++++++++++++++++++ src/oVCXBridge.sol | 114 ++++++++++++++++++++++++++++++++++++++++ src/oVCX_L2.sol | 22 ++++++++ 4 files changed, 232 insertions(+) create mode 100644 script/DeployOVCX.s.sol create mode 100644 src/oVCXBridge.sol create mode 100644 src/oVCX_L2.sol diff --git a/foundry.toml b/foundry.toml index 883a4a0..cddbe5a 100644 --- a/foundry.toml +++ b/foundry.toml @@ -9,7 +9,12 @@ remappings = [ # Foundry fails to compute the correct path. By adding it to the root # project's remappings we can work around the issue. If it's fixed in the future we should update the repo. 'openzeppelin-contracts-upgradeable/=lib/popcorn/lib/openzeppelin-contracts-upgradeable/contracts', + '@layerzerolabs/oft-evm/=lib/devtools/packages/oft-evm/', + '@layerzerolabs/oapp-evm/=lib/devtools/packages/oapp-evm/', + '@layerzerolabs/lz-evm-protocol-v2/=lib/layerzero-v2/packages/layerzero-v2/evm/protocol', + '@popcorn/=lib/popcorn/' ] + verbosity = 1 evm_version = "shanghai" diff --git a/script/DeployOVCX.s.sol b/script/DeployOVCX.s.sol new file mode 100644 index 0000000..8905a7f --- /dev/null +++ b/script/DeployOVCX.s.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.25; + +import {Script, console} from "forge-std/Script.sol"; +import {oVCXBridge} from "../src/oVCXBridge.sol"; +import {oVCXL2} from "../src/oVCX_L2.sol"; + +// deploy oVCX mainnet bridge +contract DeployOVCXBridge is Script { + function run() public returns (oVCXBridge bridge) { + vm.startBroadcast(); + console.log("msg.sender:", msg.sender); + + // oVCX address + address oVCXToken = address(0); + + // mainnet LZ Endpoint + address lzEndpoint = address(address(0)); + + // deploy - sets msg.sender as owner + bridge = new oVCXBridge(oVCXToken, lzEndpoint, msg.sender); + + vm.stopBroadcast(); + } +} + +// deploys oVCX on L2 and sets peer with mainnet oVCX bridge +contract DeployOVCXL2 is Script { + function run() public returns (oVCXL2 oVCX) { + uint32 mainnetEndpointID = 40232; // mainnet EndPoint ID with oVCX bridge adapter + uint32 l2EndpointID = 40231; // endpoint id of the l2 oVCX being deployed + + vm.startBroadcast(); + console.log("msg.sender:", msg.sender); + + // L2 chain LZ contract address + address lzEndpoint = address(0); // modify + + // mainnet oVCX bridge adapter + address bridge = address(0); // modify + + // deploy L2 oVCX - sets msg.sender as owner + oVCX = new oVCXL2("VCX call option token", "oVCX", lzEndpoint); + + // register l2 -> mainnet peer + oVCX.setPeer(mainnetEndpointID, addressToBytes32(bridge)); + + // register mainnet -> l2 peer + oVCXBridge(bridge).setPeer( + l2EndpointID, + addressToBytes32(address(oVCX)) + ); + + vm.stopBroadcast(); + } + + function addressToBytes32(address _addr) public pure returns (bytes32) { + return bytes32(uint256(uint160(_addr))); + } +} + +// register new peer - add new chain +contract AddChain is Script { + function run() public { + vm.startBroadcast(); + console.log("msg.sender:", msg.sender); + + // mainnet oVCX bridge + oVCXBridge bridge = oVCXBridge(address(0)); + uint32 mainnetEndpointID = 40232; // mainnet EndPoint ID with oVCX bridge adapter + + // l2 oVCX to register + uint32 l2EndpointID = 40231; // arbitrum sepolia eID + address oVCX = address(0); + + // register mainnet -> l2 peer + bridge.setPeer(l2EndpointID, addressToBytes32(oVCX)); + + // register l2 -> mainnet peer + oVCXL2(oVCX).setPeer( + mainnetEndpointID, + addressToBytes32(address(bridge)) + ); + + vm.stopBroadcast(); + } + + function addressToBytes32(address _addr) public pure returns (bytes32) { + return bytes32(uint256(uint160(_addr))); + } +} diff --git a/src/oVCXBridge.sol b/src/oVCXBridge.sol new file mode 100644 index 0000000..d58567a --- /dev/null +++ b/src/oVCXBridge.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.22; + +import {OFTAdapter} from "@layerzerolabs/oft-evm/contracts/OFTAdapter.sol"; +import {SendParam, MessagingFee, OFTMsgCodec} from "@layerzerolabs/oft-evm/contracts/OFTCore.sol"; +import {IBridger} from "./interfaces/IBridger.sol"; + +// Bridger contract to lock and bridge oVCX emissions to any chain +// implements IBridger iface to be compatible with gauges +contract oVCXBridge is OFTAdapter, IBridger { + mapping(address => uint32) public supportedGauges; + + // todo hardcode ovcx addr? + constructor( + address _token, + address _lzEndpoint, + address _owner + ) OFTAdapter(_token, _lzEndpoint, _owner) {} + + // set a layer zero endpoint ID for bridging gauge rewards + function addGauge(address gauge, uint32 destEid) external onlyOwner { + supportedGauges[gauge] = destEid; + } + + // estimate msg.value needed for a a generic ovcx bridge call // TODO + function cost() external view returns (uint256) {} + + // get msg.value necessary to bridge + function quote( + address destinationGauge, + uint256 amount + ) external view returns (uint256 value) { + uint32 destEid = supportedGauges[destinationGauge]; + require(destEid != 0, "Add gauge"); + + _quoteSend( + SendParam( + destEid, + _addressToBytes32(destinationGauge), + amount, + amount, + hex"", + hex"", + hex"" + ), + false + ); + } + + function bridge( + address token, + address dest, + uint256 amount + ) external payable { + // TODO token check ? + + uint32 destEid = supportedGauges[dest]; + require(destEid != 0, "Add gauge"); + + // lock tokens in the adapter + (uint256 amountSentLD, ) = _debit(msg.sender, amount, amount, destEid); + + // build LZ message and options + (bytes memory message, bytes memory options) = _getMessageAndOptions( + _addressToBytes32(dest), + amountSentLD + ); + + // Sends the message to the LayerZero endpoint + _lzSend( + destEid, + message, + options, + MessagingFee(msg.value, 0), + msg.sender + ); + } + + function _quoteSend( + SendParam memory _sendParam, + bool _payInLzToken + ) internal view returns (MessagingFee memory msgFee) { + // simulate a debit call + (uint256 amountSendLD, uint256 amountReceivedLD) = _debitView( + _sendParam.amountLD, + _sendParam.minAmountLD, + _sendParam.dstEid + ); + + // build LZ message and options + (bytes memory message, bytes memory options) = _getMessageAndOptions( + _sendParam.to, + amountSendLD + ); + + // Calculates the LayerZero fee + return _quote(_sendParam.dstEid, message, options, _payInLzToken); + } + + function _getMessageAndOptions( + bytes32 destAddr, + uint256 amountSentLD + ) internal view returns (bytes memory m, bytes memory o) { + (m, ) = OFTMsgCodec.encode(destAddr, _toSD(amountSentLD), hex""); + + // hardcoded options for a vanilla OFT bridge + o = hex"0003010011010000000000000000000000000000ea60"; + } + + function _addressToBytes32(address _addr) internal pure returns (bytes32) { + return bytes32(uint256(uint160(_addr))); + } +} diff --git a/src/oVCX_L2.sol b/src/oVCX_L2.sol new file mode 100644 index 0000000..ca2f82f --- /dev/null +++ b/src/oVCX_L2.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.22; + +import { OFT } from "@layerzerolabs/oft-evm/contracts/OFT.sol"; + +// oVCX on L2: ERC20 + Layer Zero receiver +contract oVCXL2 is OFT { + mapping(address => uint32) public supportedGauges; + + // todo hardcode ovcx addr? + constructor( + string memory _name, + string memory _symbol, + address _lzEndpoint + ) OFT(_name, _symbol, _lzEndpoint, msg.sender) {} + + // set a layer zero endpoint ID for bridging gauge rewards + function addGauge(address gauge, uint32 destEid) external onlyOwner { + supportedGauges[gauge] = destEid; + } +} \ No newline at end of file From 2ed15074e453eb03152aecbf9fe04a4c707a59ba Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 19 Dec 2024 16:15:35 +0100 Subject: [PATCH 4/5] Working oVCX bridger --- script/DeployOVCX.s.sol | 116 +++++++++++++++++++++++------------- src/interfaces/IBridger.sol | 1 + src/oVCXBridge.sol | 86 +++++++++++++++----------- src/oVCX_L2.sol | 8 +-- 4 files changed, 129 insertions(+), 82 deletions(-) diff --git a/script/DeployOVCX.s.sol b/script/DeployOVCX.s.sol index 8905a7f..15c3757 100644 --- a/script/DeployOVCX.s.sol +++ b/script/DeployOVCX.s.sol @@ -2,53 +2,83 @@ pragma solidity ^0.8.25; import {Script, console} from "forge-std/Script.sol"; -import {oVCXBridge} from "../src/oVCXBridge.sol"; +import {OVCXBridge} from "../src/oVCXBridge.sol"; import {oVCXL2} from "../src/oVCX_L2.sol"; -// deploy oVCX mainnet bridge +// 1 - DeployOVCXBridge on root chain +// 2 - WhitelistReceiver on root chain +// 3 - DeployOVCXL2 on recipient chain +// 4 - SetReceiverPeer on root chain + +// ------------------------------- ROOT LZ APP ------------------------------- // + +// deploy LZ oVCX Root bridge contract DeployOVCXBridge is Script { - function run() public returns (oVCXBridge bridge) { + function run() public returns (OVCXBridge bridge) { vm.startBroadcast(); console.log("msg.sender:", msg.sender); // oVCX address - address oVCXToken = address(0); + address tokenToBridge = address( + 0xb1D4538B4571d411F07960EF2838Ce337FE1E80E + ); // modify - // mainnet LZ Endpoint - address lzEndpoint = address(address(0)); + // see https://docs.layerzero.network/v2/developers/evm/technical-reference/deployed-contracts + address lzEndpoint = address( + 0x6EDCE65403992e310A62460808c4b910D972f10f + ); // modify - // deploy - sets msg.sender as owner - bridge = new oVCXBridge(oVCXToken, lzEndpoint, msg.sender); + // deploy + address owner = msg.sender; + bridge = new OVCXBridge(tokenToBridge, lzEndpoint, owner); vm.stopBroadcast(); } } -// deploys oVCX on L2 and sets peer with mainnet oVCX bridge -contract DeployOVCXL2 is Script { - function run() public returns (oVCXL2 oVCX) { - uint32 mainnetEndpointID = 40232; // mainnet EndPoint ID with oVCX bridge adapter - uint32 l2EndpointID = 40231; // endpoint id of the l2 oVCX being deployed - +// to whitelist new address that can receive crosschain token from bridge +contract WhitelistReceiver is Script { + function run() public { vm.startBroadcast(); console.log("msg.sender:", msg.sender); - // L2 chain LZ contract address - address lzEndpoint = address(0); // modify + // initialize bridge + OVCXBridge bridge = OVCXBridge( + address(0x545C512775aad7541C9388E0cBF1aD300658Ee92) + ); // modify - // mainnet oVCX bridge adapter - address bridge = address(0); // modify + // whitelist receiver and LZ endpoint id + address receiverToWhitelist = address( + 0x9bE75Bc132923847290677328b8FFB15d3081f2c + ); // modify + uint32 destinationEndpointId = 40232; // modify - // deploy L2 oVCX - sets msg.sender as owner - oVCX = new oVCXL2("VCX call option token", "oVCX", lzEndpoint); + bridge.addGauge(receiverToWhitelist, destinationEndpointId); - // register l2 -> mainnet peer - oVCX.setPeer(mainnetEndpointID, addressToBytes32(bridge)); + vm.stopBroadcast(); + } +} - // register mainnet -> l2 peer - oVCXBridge(bridge).setPeer( - l2EndpointID, - addressToBytes32(address(oVCX)) +// to register a new chain: set peer with LZ receiver contract +contract SetReceiverPeer is Script { + function run() public { + vm.startBroadcast(); + console.log("msg.sender:", msg.sender); + + // initialize bridge + OVCXBridge bridge = OVCXBridge( + address(0x545C512775aad7541C9388E0cBF1aD300658Ee92) + ); // modify + + // set LZ peer to receiver app + uint32 destinationEndpointId = 40232; // LZ receiver endpoint ID + address destinationLZPeer = address( + 0x870C872320d599fE6C9158C9DddeA01A08F2aE1c + ); // LZ receiver app + + bridge.setPeer( + destinationEndpointId, + addressToBytes32(destinationLZPeer) ); vm.stopBroadcast(); @@ -59,28 +89,30 @@ contract DeployOVCXL2 is Script { } } -// register new peer - add new chain -contract AddChain is Script { - function run() public { +// ------------------------------- RECEIVER LZ APP ------------------------------- // + +// deploys oVCX on L2 and sets peer with root oVCX bridge +contract DeployOVCXL2 is Script { + function run() public returns (oVCXL2 oVCX) { + uint32 receiverEndpointID = 40232; // LZ endpointID of the receiver being deployed + uint32 rootEndpointID = 40231; // LZ endpointID of the root bridge + vm.startBroadcast(); console.log("msg.sender:", msg.sender); - // mainnet oVCX bridge - oVCXBridge bridge = oVCXBridge(address(0)); - uint32 mainnetEndpointID = 40232; // mainnet EndPoint ID with oVCX bridge adapter + // see https://docs.layerzero.network/v2/developers/evm/technical-reference/deployed-contracts + address lzEndpoint = address( + 0x6EDCE65403992e310A62460808c4b910D972f10f + ); // modify - // l2 oVCX to register - uint32 l2EndpointID = 40231; // arbitrum sepolia eID - address oVCX = address(0); + // root bridge address + address bridge = address(0x545C512775aad7541C9388E0cBF1aD300658Ee92); // modify - // register mainnet -> l2 peer - bridge.setPeer(l2EndpointID, addressToBytes32(oVCX)); + // deploy L2 oVCX - sets msg.sender as owner + oVCX = new oVCXL2("VCX call option token", "oVCX", lzEndpoint); - // register l2 -> mainnet peer - oVCXL2(oVCX).setPeer( - mainnetEndpointID, - addressToBytes32(address(bridge)) - ); + // set peer with root bridge + oVCX.setPeer(rootEndpointID, addressToBytes32(bridge)); vm.stopBroadcast(); } diff --git a/src/interfaces/IBridger.sol b/src/interfaces/IBridger.sol index 8e61289..09fff63 100644 --- a/src/interfaces/IBridger.sol +++ b/src/interfaces/IBridger.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.0; interface IBridger { + function check(address toCheck) external view returns (bool); function cost() external view returns (uint256); function bridge(address token, address dest, uint256 amount) external payable; } diff --git a/src/oVCXBridge.sol b/src/oVCXBridge.sol index d58567a..d10257c 100644 --- a/src/oVCXBridge.sol +++ b/src/oVCXBridge.sol @@ -8,9 +8,11 @@ import {IBridger} from "./interfaces/IBridger.sol"; // Bridger contract to lock and bridge oVCX emissions to any chain // implements IBridger iface to be compatible with gauges -contract oVCXBridge is OFTAdapter, IBridger { +contract OVCXBridge is OFTAdapter, IBridger { mapping(address => uint32) public supportedGauges; + address public defaultGauge; // Todo + // todo hardcode ovcx addr? constructor( address _token, @@ -18,43 +20,34 @@ contract oVCXBridge is OFTAdapter, IBridger { address _owner ) OFTAdapter(_token, _lzEndpoint, _owner) {} - // set a layer zero endpoint ID for bridging gauge rewards + // whitelist a destination address (gauge) function addGauge(address gauge, uint32 destEid) external onlyOwner { + defaultGauge = gauge; supportedGauges[gauge] = destEid; } - // estimate msg.value needed for a a generic ovcx bridge call // TODO - function cost() external view returns (uint256) {} + // TODO + // implements IBridger iface - check the originator sender + function check(address) external view returns (bool) { + return true; + } + + // estimate msg.value needed for a default bridge call // TODO + // implements IBridger iface + function cost() external view returns (uint256) { + return _quoteSend(defaultGauge, 1e18); + } // get msg.value necessary to bridge function quote( address destinationGauge, uint256 amount ) external view returns (uint256 value) { - uint32 destEid = supportedGauges[destinationGauge]; - require(destEid != 0, "Add gauge"); - - _quoteSend( - SendParam( - destEid, - _addressToBytes32(destinationGauge), - amount, - amount, - hex"", - hex"", - hex"" - ), - false - ); + return _quoteSend(destinationGauge, amount); } - function bridge( - address token, - address dest, - uint256 amount - ) external payable { - // TODO token check ? - + // bridges to the destination address via LZ + function bridge(address, address dest, uint256 amount) external payable { uint32 destEid = supportedGauges[dest]; require(destEid != 0, "Add gauge"); @@ -77,25 +70,46 @@ contract oVCXBridge is OFTAdapter, IBridger { ); } + // builds crosschain message and ask for a quote to LZ endpoint function _quoteSend( - SendParam memory _sendParam, - bool _payInLzToken - ) internal view returns (MessagingFee memory msgFee) { + address receiver, + uint256 amount + ) internal view returns (uint256) { + uint32 destinationEndpointId = supportedGauges[receiver]; + require(destinationEndpointId != 0, "Add gauge"); + + SendParam memory sendParams = SendParam( + destinationEndpointId, + _addressToBytes32(receiver), + amount, + amount, + hex"", + hex"", + hex"" + ); + // simulate a debit call - (uint256 amountSendLD, uint256 amountReceivedLD) = _debitView( - _sendParam.amountLD, - _sendParam.minAmountLD, - _sendParam.dstEid + (uint256 amountSendLD, ) = _debitView( + sendParams.amountLD, + sendParams.minAmountLD, + sendParams.dstEid ); // build LZ message and options (bytes memory message, bytes memory options) = _getMessageAndOptions( - _sendParam.to, + sendParams.to, amountSendLD ); - // Calculates the LayerZero fee - return _quote(_sendParam.dstEid, message, options, _payInLzToken); + // quote LZ endpoint for a fee value + MessagingFee memory fee = _quote( + sendParams.dstEid, + message, + options, + false + ); + + return fee.nativeFee; } function _getMessageAndOptions( diff --git a/src/oVCX_L2.sol b/src/oVCX_L2.sol index ca2f82f..7a0762f 100644 --- a/src/oVCX_L2.sol +++ b/src/oVCX_L2.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.22; -import { OFT } from "@layerzerolabs/oft-evm/contracts/OFT.sol"; +import {OFT} from "@layerzerolabs/oft-evm/contracts/OFT.sol"; -// oVCX on L2: ERC20 + Layer Zero receiver +// oVCX on L2: ERC20 + Layer Zero receiver contract oVCXL2 is OFT { mapping(address => uint32) public supportedGauges; - // todo hardcode ovcx addr? + // todo check crosschain origin to be a supported gauge? constructor( string memory _name, string memory _symbol, @@ -19,4 +19,4 @@ contract oVCXL2 is OFT { function addGauge(address gauge, uint32 destEid) external onlyOwner { supportedGauges[gauge] = destEid; } -} \ No newline at end of file +} From 9ab3d0b36e9802cef60b98d6b3f1a54cae548c03 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 20 Dec 2024 15:41:13 +0100 Subject: [PATCH 5/5] Fix remaining issues and naming --- script/DeployOVCX.s.sol | 54 +++++++++++++++++-------- src/{oVCX_L2.sol => oVCX_Recipient.sol} | 16 ++++++-- src/{oVCXBridge.sol => oVCX_Sender.sol} | 20 ++++++--- 3 files changed, 64 insertions(+), 26 deletions(-) rename src/{oVCX_L2.sol => oVCX_Recipient.sol} (54%) rename src/{oVCXBridge.sol => oVCX_Sender.sol} (86%) diff --git a/script/DeployOVCX.s.sol b/script/DeployOVCX.s.sol index 15c3757..541325a 100644 --- a/script/DeployOVCX.s.sol +++ b/script/DeployOVCX.s.sol @@ -2,19 +2,20 @@ pragma solidity ^0.8.25; import {Script, console} from "forge-std/Script.sol"; -import {OVCXBridge} from "../src/oVCXBridge.sol"; -import {oVCXL2} from "../src/oVCX_L2.sol"; +import {oVCXSender} from "../src/oVCX_Sender.sol"; +import {oVCXRecipient} from "../src/oVCX_Recipient.sol"; // 1 - DeployOVCXBridge on root chain -// 2 - WhitelistReceiver on root chain +// 2 - WhitelistDestination on root chain // 3 - DeployOVCXL2 on recipient chain // 4 - SetReceiverPeer on root chain +// 5 - WhitelistReceiver on recipient chain // ------------------------------- ROOT LZ APP ------------------------------- // // deploy LZ oVCX Root bridge contract DeployOVCXBridge is Script { - function run() public returns (OVCXBridge bridge) { + function run() public returns (oVCXSender bridge) { vm.startBroadcast(); console.log("msg.sender:", msg.sender); @@ -30,27 +31,25 @@ contract DeployOVCXBridge is Script { // deploy address owner = msg.sender; - bridge = new OVCXBridge(tokenToBridge, lzEndpoint, owner); + bridge = new oVCXSender(tokenToBridge, lzEndpoint, owner); vm.stopBroadcast(); } } // to whitelist new address that can receive crosschain token from bridge -contract WhitelistReceiver is Script { +contract WhitelistDestination is Script { function run() public { vm.startBroadcast(); console.log("msg.sender:", msg.sender); // initialize bridge - OVCXBridge bridge = OVCXBridge( - address(0x545C512775aad7541C9388E0cBF1aD300658Ee92) + oVCXSender bridge = oVCXSender( + address(0x3C54b43FEC8f796911ef7e74A375aE9d8E5c2f51) ); // modify // whitelist receiver and LZ endpoint id - address receiverToWhitelist = address( - 0x9bE75Bc132923847290677328b8FFB15d3081f2c - ); // modify + address receiverToWhitelist = address(0); // modify uint32 destinationEndpointId = 40232; // modify bridge.addGauge(receiverToWhitelist, destinationEndpointId); @@ -66,14 +65,14 @@ contract SetReceiverPeer is Script { console.log("msg.sender:", msg.sender); // initialize bridge - OVCXBridge bridge = OVCXBridge( - address(0x545C512775aad7541C9388E0cBF1aD300658Ee92) + oVCXSender bridge = oVCXSender( + address(0x3C54b43FEC8f796911ef7e74A375aE9d8E5c2f51) ); // modify // set LZ peer to receiver app uint32 destinationEndpointId = 40232; // LZ receiver endpoint ID address destinationLZPeer = address( - 0x870C872320d599fE6C9158C9DddeA01A08F2aE1c + 0x8f3C2301238e3e03C0e402aB078F9aBcfF422ADD ); // LZ receiver app bridge.setPeer( @@ -93,7 +92,7 @@ contract SetReceiverPeer is Script { // deploys oVCX on L2 and sets peer with root oVCX bridge contract DeployOVCXL2 is Script { - function run() public returns (oVCXL2 oVCX) { + function run() public returns (oVCXRecipient oVCX) { uint32 receiverEndpointID = 40232; // LZ endpointID of the receiver being deployed uint32 rootEndpointID = 40231; // LZ endpointID of the root bridge @@ -106,10 +105,10 @@ contract DeployOVCXL2 is Script { ); // modify // root bridge address - address bridge = address(0x545C512775aad7541C9388E0cBF1aD300658Ee92); // modify + address bridge = address(0x3C54b43FEC8f796911ef7e74A375aE9d8E5c2f51); // modify // deploy L2 oVCX - sets msg.sender as owner - oVCX = new oVCXL2("VCX call option token", "oVCX", lzEndpoint); + oVCX = new oVCXRecipient("VCX call option token", "oVCX", lzEndpoint); // set peer with root bridge oVCX.setPeer(rootEndpointID, addressToBytes32(bridge)); @@ -121,3 +120,24 @@ contract DeployOVCXL2 is Script { return bytes32(uint256(uint160(_addr))); } } + +// to whitelist new address that can receive crosschain token from bridge +contract WhitelistReceiver is Script { + function run() public { + vm.startBroadcast(); + console.log("msg.sender:", msg.sender); + + // initialize recipient contract + oVCXRecipient receiverBridge = oVCXRecipient( + address(0x8f3C2301238e3e03C0e402aB078F9aBcfF422ADD) + ); // modify + + // whitelist receiver and LZ endpoint id + address receiverToWhitelist = address(0); // modify + uint32 sourceEndpointId = 40231; // modify + + receiverBridge.addGauge(receiverToWhitelist, sourceEndpointId); + + vm.stopBroadcast(); + } +} diff --git a/src/oVCX_L2.sol b/src/oVCX_Recipient.sol similarity index 54% rename from src/oVCX_L2.sol rename to src/oVCX_Recipient.sol index 7a0762f..89fa33b 100644 --- a/src/oVCX_L2.sol +++ b/src/oVCX_Recipient.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.22; import {OFT} from "@layerzerolabs/oft-evm/contracts/OFT.sol"; // oVCX on L2: ERC20 + Layer Zero receiver -contract oVCXL2 is OFT { +contract oVCXRecipient is OFT { mapping(address => uint32) public supportedGauges; // todo check crosschain origin to be a supported gauge? @@ -16,7 +16,17 @@ contract oVCXL2 is OFT { ) OFT(_name, _symbol, _lzEndpoint, msg.sender) {} // set a layer zero endpoint ID for bridging gauge rewards - function addGauge(address gauge, uint32 destEid) external onlyOwner { - supportedGauges[gauge] = destEid; + function addGauge(address gauge, uint32 srcEid) external onlyOwner { + supportedGauges[gauge] = srcEid; + } + + function _credit( + address _to, + uint256 _amountLD, + uint32 _srcEid + ) internal override returns (uint256 amountReceivedLD) { + require(supportedGauges[_to] == _srcEid, "Invalid sender"); + + return super._credit(_to, _amountLD, _srcEid); } } diff --git a/src/oVCXBridge.sol b/src/oVCX_Sender.sol similarity index 86% rename from src/oVCXBridge.sol rename to src/oVCX_Sender.sol index d10257c..ac8a9d7 100644 --- a/src/oVCXBridge.sol +++ b/src/oVCX_Sender.sol @@ -8,12 +8,11 @@ import {IBridger} from "./interfaces/IBridger.sol"; // Bridger contract to lock and bridge oVCX emissions to any chain // implements IBridger iface to be compatible with gauges -contract OVCXBridge is OFTAdapter, IBridger { +contract oVCXSender is OFTAdapter, IBridger { mapping(address => uint32) public supportedGauges; - address public defaultGauge; // Todo + address public defaultGauge; // used to estimate a "default" bridging cost - // todo hardcode ovcx addr? constructor( address _token, address _lzEndpoint, @@ -22,23 +21,32 @@ contract OVCXBridge is OFTAdapter, IBridger { // whitelist a destination address (gauge) function addGauge(address gauge, uint32 destEid) external onlyOwner { + if(defaultGauge == address(0)) + defaultGauge = gauge; + + supportedGauges[gauge] = destEid; + } + + // update default gauge + function setDefaultGauge(address gauge, uint32 destEid) external onlyOwner { defaultGauge = gauge; + supportedGauges[gauge] = destEid; } - // TODO + // called by RootGaugeFactory with msg.sender originating the tx // implements IBridger iface - check the originator sender function check(address) external view returns (bool) { return true; } - // estimate msg.value needed for a default bridge call // TODO + // called by RootGaugeFactory to estimate msg.value necessary for bridging // implements IBridger iface function cost() external view returns (uint256) { return _quoteSend(defaultGauge, 1e18); } - // get msg.value necessary to bridge + // get msg.value necessary to bridge to a speficic chain function quote( address destinationGauge, uint256 amount