Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@
[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
[submodule "lib/layerzero-v2"]
path = lib/layerzero-v2
url = https://github.com/LayerZero-Labs/layerzero-v2
5 changes: 5 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
1 change: 1 addition & 0 deletions lib/devtools
Submodule devtools added at 108f4d
1 change: 1 addition & 0 deletions lib/layerzero-v2
Submodule layerzero-v2 added at 34321a
143 changes: 143 additions & 0 deletions script/DeployOVCX.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.25;

import {Script, console} from "forge-std/Script.sol";
import {oVCXSender} from "../src/oVCX_Sender.sol";
import {oVCXRecipient} from "../src/oVCX_Recipient.sol";

// 1 - DeployOVCXBridge 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 (oVCXSender bridge) {
vm.startBroadcast();
console.log("msg.sender:", msg.sender);

// oVCX address
address tokenToBridge = address(
0xb1D4538B4571d411F07960EF2838Ce337FE1E80E
); // modify

// see https://docs.layerzero.network/v2/developers/evm/technical-reference/deployed-contracts
address lzEndpoint = address(
0x6EDCE65403992e310A62460808c4b910D972f10f
); // modify

// deploy
address owner = msg.sender;
bridge = new oVCXSender(tokenToBridge, lzEndpoint, owner);

vm.stopBroadcast();
}
}

// to whitelist new address that can receive crosschain token from bridge
contract WhitelistDestination is Script {
function run() public {
vm.startBroadcast();
console.log("msg.sender:", msg.sender);

// initialize bridge
oVCXSender bridge = oVCXSender(
address(0x3C54b43FEC8f796911ef7e74A375aE9d8E5c2f51)
); // modify

// whitelist receiver and LZ endpoint id
address receiverToWhitelist = address(0); // modify
uint32 destinationEndpointId = 40232; // modify

bridge.addGauge(receiverToWhitelist, destinationEndpointId);

vm.stopBroadcast();
}
}

// 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
oVCXSender bridge = oVCXSender(
address(0x3C54b43FEC8f796911ef7e74A375aE9d8E5c2f51)
); // modify

// set LZ peer to receiver app
uint32 destinationEndpointId = 40232; // LZ receiver endpoint ID
address destinationLZPeer = address(
0x8f3C2301238e3e03C0e402aB078F9aBcfF422ADD
); // LZ receiver app

bridge.setPeer(
destinationEndpointId,
addressToBytes32(destinationLZPeer)
);

vm.stopBroadcast();
}

function addressToBytes32(address _addr) public pure returns (bytes32) {
return bytes32(uint256(uint160(_addr)));
}
}

// ------------------------------- RECEIVER LZ APP ------------------------------- //

// deploys oVCX on L2 and sets peer with root oVCX bridge
contract DeployOVCXL2 is Script {
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

vm.startBroadcast();
console.log("msg.sender:", msg.sender);

// see https://docs.layerzero.network/v2/developers/evm/technical-reference/deployed-contracts
address lzEndpoint = address(
0x6EDCE65403992e310A62460808c4b910D972f10f
); // modify

// root bridge address
address bridge = address(0x3C54b43FEC8f796911ef7e74A375aE9d8E5c2f51); // modify

// deploy L2 oVCX - sets msg.sender as owner
oVCX = new oVCXRecipient("VCX call option token", "oVCX", lzEndpoint);

// set peer with root bridge
oVCX.setPeer(rootEndpointID, addressToBytes32(bridge));

vm.stopBroadcast();
}

function addressToBytes32(address _addr) public pure returns (bytes32) {
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();
}
}
1 change: 1 addition & 0 deletions src/interfaces/IBridger.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
32 changes: 32 additions & 0 deletions src/oVCX_Recipient.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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 oVCXRecipient is OFT {
mapping(address => uint32) public supportedGauges;

// todo check crosschain origin to be a supported gauge?
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 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);
}
}
136 changes: 136 additions & 0 deletions src/oVCX_Sender.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// 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 oVCXSender is OFTAdapter, IBridger {
mapping(address => uint32) public supportedGauges;

address public defaultGauge; // used to estimate a "default" bridging cost

constructor(
address _token,
address _lzEndpoint,
address _owner
) OFTAdapter(_token, _lzEndpoint, _owner) {}

// 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;
}

// 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;
}

// 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 to a speficic chain
function quote(
address destinationGauge,
uint256 amount
) external view returns (uint256 value) {
return _quoteSend(destinationGauge, amount);
}

// 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");

// 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
);
}

// builds crosschain message and ask for a quote to LZ endpoint
function _quoteSend(
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, ) = _debitView(
sendParams.amountLD,
sendParams.minAmountLD,
sendParams.dstEid
);

// build LZ message and options
(bytes memory message, bytes memory options) = _getMessageAndOptions(
sendParams.to,
amountSendLD
);

// quote LZ endpoint for a fee value
MessagingFee memory fee = _quote(
sendParams.dstEid,
message,
options,
false
);

return fee.nativeFee;
}

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)));
}
}