Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AxelarBridgeGovernor #354

Closed
wants to merge 2 commits into from
Closed
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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@
[submodule "lib/gelato-automate"]
path = lib/gelato-automate
url = https://github.com/gelatodigital/automate
[submodule "lib/axelar-gmp-sdk-solidity"]
path = lib/axelar-gmp-sdk-solidity
url = https://github.com/axelarnetwork/axelar-gmp-sdk-solidity
1 change: 1 addition & 0 deletions lib/axelar-gmp-sdk-solidity
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ forge-std/=lib/forge-std/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/
layer-zero-v2/=lib/LayerZero-v2/
gelato-automate/=lib/gelato-automate/contracts/
axelar/=lib/axelar-gmp-sdk-solidity/contracts/
128 changes: 128 additions & 0 deletions scripts/DeployAxelarBridgedGovernor.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.20;

import {console, Script} from "forge-std/Script.sol";
import {AxelarBridgedGovernor, BridgedGovernorProxy, Call} from "src/BridgedGovernor.sol";
import {IAxelarGasService} from "axelar/interfaces/IAxelarGasService.sol";
import {IAxelarGMPGateway} from "axelar/interfaces/IAxelarGMPGateway.sol";
import {AddressToString} from "axelar/libs/AddressString.sol";

string constant SEPOLIA_CHAIN_NAME = "ethereum-sepolia";
string constant BSC_TESTNET_CHAIN_NAME = "binance";

IAxelarGMPGateway constant SEPOLIA_GATEWAY =
IAxelarGMPGateway(0xe432150cce91c13a887f7D836923d5597adD8E31);
IAxelarGasService constant BSC_TESTNET_GAS_SERVICE =
IAxelarGasService(0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6);
IAxelarGMPGateway constant BSC_TESTNET_GATEWAY =
IAxelarGMPGateway(0x4D147dCb984e6affEEC47e44293DA442580A3Ec0);

// forge script scripts/DeployAxelarBridgedGovernor.s.sol:DeployToBscTestnet $WALLET_ARGS -f "$ETH_RPC_URL"

// contract DeployToBscTestnet is Script {
// function run() public {
// address owner = vm.envOr("OWNER", msg.sender);

// require(block.chainid == 97, "Must be run on BSC testnet");
// vm.startBroadcast();
// AxelarBridgedGovernor logic =
// new AxelarBridgedGovernor(BSC_TESTNET_GATEWAY, SEPOLIA_CHAIN_NAME, owner);
// BridgedGovernorProxy governor = new BridgedGovernorProxy(address(logic), new Call[](0));
// vm.stopBroadcast();
// console.log("Deployed AxelarBridgedGovernor:", address(governor));
// }
// }


// Gateway and ddresses taken from https://docs.axelar.dev/resources/contract-addresses/testnet

// Run on BSC testnet
// forge create $WALLET_ARGS scripts/DeployAxelarBridgedGovernor.s.sol:ContractCaller \
// --constructor-args 0x4D147dCb984e6affEEC47e44293DA442580A3Ec0 0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6

// Run on Sepolia
// OWNER=0xdACfE6Bf5A06953EccC3755758C5aDFfed94e147 \
// GATEWAY=0xe432150cce91c13a887f7D836923d5597adD8E31 \
// SOURCE_CHAIN=binance \
// forge script scripts/DeployAxelarBridgedGovernor.s.sol:DeployGovernor $WALLET_ARGS -f "$ETH_RPC_URL"

// Run on BSC testnet
// cast send $WALLET_ARGS 0xdACfE6Bf5A06953EccC3755758C5aDFfed94e147 \
// 'setRecipient(string,address)' ethereum-sepolia 0x78EeC20c86e5f40Ceb1b651c38072DF528AE6407

// Run on BSC testnet
// CALLER=0xdACfE6Bf5A06953EccC3755758C5aDFfed94e147 \
// FEE=$(cast to-wei 0.00 eth) \
// NONCE=2 \
// forge script scripts/DeployAxelarBridgedGovernor.s.sol:ContractCall $WALLET_ARGS -f "$ETH_RPC_URL"

contract DeployGovernor is Script {
function run() public {
address owner = vm.envOr("OWNER", msg.sender);
IAxelarGMPGateway gateway = IAxelarGMPGateway(vm.envAddress("GATEWAY"));
string memory sourceChain = vm.envString("SOURCE_CHAIN");

vm.startBroadcast();
AxelarBridgedGovernor logic = new AxelarBridgedGovernor(gateway, sourceChain, owner);
BridgedGovernorProxy governor = new BridgedGovernorProxy(address(logic), new Call[](0));
vm.stopBroadcast();
console.log("Deployed AxelarBridgedGovernor:", address(governor));
}
}

contract ContractCaller {
address public immutable owner;
IAxelarGMPGateway public immutable gateway;
IAxelarGasService public immutable gasService;

string public destinationChain;
address public recipient;

constructor(IAxelarGMPGateway gateway_, IAxelarGasService gasService_) {
owner = msg.sender;
gateway = gateway_;
gasService = gasService_;
}

function setRecipient(string calldata destinationChain_, address recipient_) public {
require(msg.sender == owner, "Only owner");
destinationChain = destinationChain_;
recipient = recipient_;
}

function callContract(bytes calldata payload) payable public {
require(msg.sender == owner, "Only owner");
string memory recipient_ = AddressToString.toString(recipient);
if (msg.value > 0) {
gasService.payNativeGasForContractCall{value: msg.value}(
address(this), destinationChain, recipient_, payload, owner
);
}
gateway.callContract(destinationChain, recipient_, payload);
}
}


contract ContractCall is Script {
function run() public {
ContractCaller caller = ContractCaller(vm.envAddress("CALLER"));
uint256 fee = vm.envOr("FEE", uint(0));
uint256 nonce = vm.envUint("NONCE");

Call[] memory calls = new Call[](1);
calls[0] = Call({
target: 0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14,
data: abi.encodeWithSignature("approve(address,uint256)", address(0x1234), 100 + nonce),
value: 0
});
// calls[0] = Call({
// target: 0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14,
// data: abi.encodeWithSignature("transferFrom(address,address,uint256)",
// msg.sender, address(0xdead), 1234),
// value: 0
// });

vm.broadcast();
caller.callContract{value: fee}(abi.encode(AxelarBridgedGovernor.Message(nonce, calls)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ pragma solidity ^0.8.20;

import {console, Script} from "forge-std/Script.sol";
import {ExecutorOptions} from "layer-zero-v2/protocol/contracts/messagelib/libs/ExecutorOptions.sol";
// import {OptionsBuilder} from "layer-zero-v2/oapp/contracts/oapp/libs/OptionsBuilder.sol";
import {
ILayerZeroEndpointV2,
IMessageLibManager,
Expand All @@ -13,7 +12,7 @@ import {
import {SetConfigParam} from "layer-zero-v2/protocol/contracts/interfaces/IMessageLibManager.sol";
import {Constant} from "layer-zero-v2/messagelib/test/util/Constant.sol";
import {Strings} from "openzeppelin-contracts/utils/Strings.sol";
import {BridgedGovernor, BridgedGovernorProxy, Call} from "src/BridgedGovernor.sol";
import {LZBridgedGovernor, BridgedGovernorProxy, Call} from "src/BridgedGovernor.sol";

// Taken from layer-zero-v2/messagelib/contracts/uln/UlnBase.sol
struct UlnConfig {
Expand Down Expand Up @@ -65,7 +64,7 @@ function addressToBytes32(address addr) pure returns (bytes32) {
return bytes32(uint256(uint160(addr)));
}

// forge script scripts/DeployBridgedGovernor.s.sol:ConfigureOnSepolia $WALLET_ARGS -f "$ETH_RPC_URL"
// forge script scripts/DeployLZBridgedGovernor.s.sol:ConfigureOnSepolia $WALLET_ARGS -f "$ETH_RPC_URL"

contract ConfigureOnSepolia is Script {
function run() public {
Expand Down Expand Up @@ -100,7 +99,7 @@ contract ConfigureOnSepolia is Script {
}
}

// forge script scripts/DeployBridgedGovernor.s.sol:DeployToBscTestnet $WALLET_ARGS -f "$ETH_RPC_URL"
// forge script scripts/DeployLZBridgedGovernor.s.sol:DeployToBscTestnet $WALLET_ARGS -f "$ETH_RPC_URL"

contract DeployToBscTestnet is Script {
function run() public {
Expand Down Expand Up @@ -152,16 +151,16 @@ contract DeployToBscTestnet is Script {

vm.startBroadcast();
address governorLogic =
address(new BridgedGovernor(BSC_TESTNET_ENDPOINT, SEPOLIA_EID, owner));
address(new LZBridgedGovernor(BSC_TESTNET_ENDPOINT, SEPOLIA_EID, owner));
address governorProxy = address(new BridgedGovernorProxy(governorLogic, calls));
vm.stopBroadcast();

require(governorProxy == governor, "Invalid deployment address");
console.log("Deployed BridgedGovernor:", governor);
console.log("Deployed LZBridgedGovernor:", governor);
}
}

// forge script scripts/DeployBridgedGovernor.s.sol:SendToBscTestnet $WALLET_ARGS -f "$ETH_RPC_URL"
// forge script scripts/DeployLZBridgedGovernor.s.sol:SendToBscTestnet $WALLET_ARGS -f "$ETH_RPC_URL"

contract SendToBscTestnet is Script {
function run() public {
Expand All @@ -177,7 +176,7 @@ contract SendToBscTestnet is Script {
MessagingParams memory params = MessagingParams({
dstEid: BSC_TESTNET_EID,
receiver: addressToBytes32(0xc9241Cf4cD7d9569cA044d8202EF1080405Bc6C9),
message: abi.encode( /* nonce */ 0, /* value */ 0, calls),
message: abi.encode(LZBridgedGovernor.Message({nonce: 0, value: 0, calls: calls})),
options: messageOptions(50_000, 0),
payInLzToken: false
});
Expand Down
127 changes: 102 additions & 25 deletions src/BridgedGovernor.sol
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.20;

import {IAxelarGMPExecutable} from "axelar/interfaces/IAxelarGMPExecutable.sol";
import {IAxelarGMPGateway} from "axelar/interfaces/IAxelarGMPGateway.sol";
import {StringToAddress} from "axelar/libs/AddressString.sol";
import {
ILayerZeroReceiver,
Origin
} from "layer-zero-v2/protocol/contracts/interfaces/ILayerZeroReceiver.sol";
import {ERC1967Proxy} from "openzeppelin-contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {UUPSUpgradeable} from "openzeppelin-contracts/proxy/utils/UUPSUpgradeable.sol";
import {Address} from "openzeppelin-contracts/utils/Address.sol";
import {ShortString, ShortStrings} from "openzeppelin-contracts/utils/ShortStrings.sol";
import {Strings} from "openzeppelin-contracts/utils/Strings.sol";

/// @notice Description of a call.
/// @param target The called address.
/// @param data The calldata to be used for the call.
/// @param value The value of the call.
struct Call {
/// @notice The called address.
address target;
/// @notice The calldata to be used for the call.
bytes data;
/// @notice The value of the call.
uint256 value;
}

Expand All @@ -30,22 +35,49 @@ function runCalls(Call[] memory calls) {
}
}

/// @notice The governor executing ordered messages.
abstract contract Governor is UUPSUpgradeable {
/// @notice The required nonce in the next executed message.
uint256 public nextMessageNonce;

/// @notice Emitted when a message is executed.
/// @param nonce The nonce of the message.
event MessageExecuted(uint256 nonce);

/// @notice Executes the message.
/// @param nonce The message nonce, must be equal to `nextMessageNonce`.
/// @param calls The list of calls to run.
function _executeMessage(uint256 nonce, Call[] memory calls) internal {
require(nonce == nextMessageNonce, "Invalid message nonce");
nextMessageNonce++;
runCalls(calls);
emit MessageExecuted(nonce);
}

function _authorizeUpgrade(address /* newImplementation */ ) internal view override {
require(msg.sender == address(this), "Only upgradeable by self");
}
}

/// @notice The governor running calls received from its owner on another chain using LayerZero v2.
contract BridgedGovernor is UUPSUpgradeable, ILayerZeroReceiver {
contract LZBridgedGovernor is Governor, ILayerZeroReceiver {
/// @notice The LayerZero v2 endpoint that is allowed to execute received messages.
address public immutable endpoint;
/// @notice The EID of the chain from which the owner is allowed to send messages.
uint32 public immutable ownerEid;
/// @notice The owner address which is allowed to send messages.
bytes32 public immutable owner;

/// @notice The required nonce encoded inside the next received message.
/// This is different from the LayerZero v2 `nextNonce`.
uint256 public nextMessageNonce;

/// @notice Emitted when a message is executed.
/// @param nonce The nonce of the message.
event MessageExecuted(uint256 nonce);
/// @notice The message passed over the bridge to the governor to execute.
struct Message {
/// @notice The message nonce, must be equal to `nextMessageNonce` when executed.
/// This is independent from the LayerZero v2 `nextNonce`.
uint256 nonce;
/// @notice The minimum accepted `msg.value` passed with the message.
uint256 value;
/// @notice The list of calls to run.
Call[] calls;
}

/// @param endpoint_ The LayerZero v2 endpoint that is allowed to execute received messages.
/// @param ownerEid_ The EID of the chain from which the owner is allowed to send messages.
Expand Down Expand Up @@ -90,33 +122,78 @@ contract BridgedGovernor is UUPSUpgradeable, ILayerZeroReceiver {
/// @notice Receive a LayerZero v2 message. Callable only by `endpoint`.
/// @param origin The message origin.
/// The only allowed origin is the `owner` on the `ownerEid` chain.
/// @param message The received message.
/// It must be the abi-encoded message nonce equal to `nextMessageNonce`,
/// followed by the message value which defines the minimum accepted `msg.value`,
/// followed by the list of `Call`s that will be immediately run.
/// @param messageEncoded The received message.
/// It must be an abi-encoded `Message`, see its documentation for more details.
function lzReceive(
Origin calldata origin,
bytes32, /* guid */
bytes calldata message,
bytes calldata messageEncoded,
address, /* executor */
bytes calldata /* extraData */
) public payable override onlyProxy {
require(msg.sender == endpoint, "Must be called by the endpoint");
require(origin.srcEid == ownerEid, "Invalid message source chain");
require(origin.sender == owner, "Invalid message sender");

(uint256 nonce, uint256 value, Call[] memory calls) =
abi.decode(message, (uint256, uint256, Call[]));
require(nonce == nextMessageNonce, "Invalid message nonce");
require(msg.value >= value, "Called with too low value");
Message memory message = abi.decode(messageEncoded, (Message));
require(msg.value >= message.value, "Called with too low value");
_executeMessage(message.nonce, message.calls);
}
}

nextMessageNonce++;
runCalls(calls);
emit MessageExecuted(nonce);
/// @notice The governor running calls received from its owner on another chain using Axelar.
contract AxelarBridgedGovernor is Governor, IAxelarGMPExecutable {
/// @notice The Axelar gateway used by this contract.
IAxelarGMPGateway public immutable override gateway;
/// @notice The name of the chain from which the owner is allowed to send messages.
ShortString internal immutable _ownerChain;
/// @notice The owner address which is allowed to send messages.
address public immutable owner;

/// @notice The name of the chain from which the owner is allowed to send messages.
/// @return ownerChain_ The name of the chain.
function ownerChain() public view returns (string memory ownerChain_) {
return ShortStrings.toString(_ownerChain);
}

function _authorizeUpgrade(address /* newImplementation */ ) internal view override {
require(msg.sender == address(this), "Only upgradeable by self");
/// @notice The message passed over the bridge to the governor to execute.
struct Message {
/// @notice The message nonce, must be equal to `nextMessageNonce` when executed.
uint256 nonce;
/// @notice The list of calls to run.
Call[] calls;
}

/// @param gateway_ The Axelar gateway used by this contract.
/// @param ownerChain_ The name of the chain from which the owner is allowed to send messages.
/// @param owner_ The owner address which is allowed to send messages.
constructor(IAxelarGMPGateway gateway_, string memory ownerChain_, address owner_) {
// slither-disable-next-line missing-zero-check
gateway = gateway_;
_ownerChain = ShortStrings.toShortString(ownerChain_);
owner = owner_;
}

/// @notice Execute the specified command sent from another chain.
/// @param commandId The identifier of the command to execute.
/// @param sourceChain The name of the source chain from where the command originated.
/// @param sender The address on the source chain that sent the command.
/// @param payload The payload of the command to be executed.
/// It must be an abi-encoded `Message`, see its documentation for more details.
function execute(
bytes32 commandId,
string calldata sourceChain,
string calldata sender,
bytes calldata payload
) public {
if (!gateway.validateContractCall(commandId, sourceChain, sender, keccak256(payload))) {
revert NotApprovedByGateway();
}
require(Strings.equal(sourceChain, ownerChain()), "Invalid message source chain");
require(StringToAddress.toAddress(sender) == owner, "Invalid message sender");

Message memory message = abi.decode(payload, (Message));
_executeMessage(message.nonce, message.calls);
}
}

Expand Down
Loading