diff --git a/.gitmodules b/.gitmodules index 60045c69..fed44048 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/lib/axelar-gmp-sdk-solidity b/lib/axelar-gmp-sdk-solidity new file mode 160000 index 00000000..3157ba4f --- /dev/null +++ b/lib/axelar-gmp-sdk-solidity @@ -0,0 +1 @@ +Subproject commit 3157ba4f704de408e356ad8236b5d81bbc82d7cb diff --git a/remappings.txt b/remappings.txt index 66f181ef..19fb4506 100644 --- a/remappings.txt +++ b/remappings.txt @@ -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/ diff --git a/scripts/DeployAxelarBridgedGovernor.s.sol b/scripts/DeployAxelarBridgedGovernor.s.sol new file mode 100644 index 00000000..c43a14d0 --- /dev/null +++ b/scripts/DeployAxelarBridgedGovernor.s.sol @@ -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))); + } +} diff --git a/src/BridgedGovernor.sol b/src/BridgedGovernor.sol index bebe131c..e087bd19 100644 --- a/src/BridgedGovernor.sol +++ b/src/BridgedGovernor.sol @@ -1,6 +1,9 @@ // 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 @@ -8,6 +11,8 @@ import { 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. struct Call { @@ -136,6 +141,62 @@ contract LZBridgedGovernor is Governor, ILayerZeroReceiver { } } +/// @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); + } + + /// @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); + } +} + /// @notice The specialized proxy for `BridgedGovernor`. contract BridgedGovernorProxy is ERC1967Proxy { /// @param logic The initial address of the logic for the proxy.