diff --git a/ethereum/src/ERC20Gateway.sol b/ethereum/src/ERC20Gateway.sol index e9f66588..f37125f8 100644 --- a/ethereum/src/ERC20Gateway.sol +++ b/ethereum/src/ERC20Gateway.sol @@ -32,44 +32,40 @@ contract ERC20Gateway is IERC20Gateway, IMessageQueueReceiver { } /** @dev Accept bridging request made on other side of bridge. - * This request can be sent by `MessageQueue` only. When such a request is accpeted, tokens - * are minted to the corresponding account address, specified in `vara_msg`. + * This request must be sent by `MessageQueue` only. When such a request is accepted, tokens + * are minted to the corresponding account address, specified in `payload`. * - * Expected `payload` in `VaraMessage` consisits of these: + * Expected `payload` consisits of these: * - `receiver` - account to mint tokens to * - `token` - token to mint * - `amount` - amount of tokens to mint * - * @param vara_msg `VaraMessage` received from MessageQueue. + * Expected sender should be `vft-treasury` program on gear. + * + * @param sender sender of message on the gear side. + * @param payload payload of the message. */ function processVaraMessage( - VaraMessage calldata vara_msg + bytes32 sender, + bytes calldata payload ) external returns (bool) { - uint160 receiver; - uint160 token; - uint256 amount; if (msg.sender != MESSAGE_QUEUE_ADDRESS) { revert NotAuthorized(); } - if (vara_msg.data.length != 20 + 20 + 32) { + if (payload.length != 20 + 20 + 32) { revert BadArguments(); } - if (vara_msg.receiver != address(this)) { - revert BadEthAddress(); - } - if (vara_msg.sender != VFT_TREASURY_ADDRESS) { + if (sender != VFT_TREASURY_ADDRESS) { revert BadVaraAddress(); } - assembly { - receiver := shr(96, calldataload(0xC4)) - token := shr(96, calldataload(0xD8)) - amount := calldataload(0xEC) - } + address receiver = address(bytes20(payload[:20])); + address token = address(bytes20(payload[20:40])); + uint256 amount = uint256(bytes32(payload[40:])); - ERC20VaraSupply(address(token)).mint(address(receiver), amount); + ERC20VaraSupply(token).mint(receiver, amount); + emit BridgingAccepted(receiver, token, amount); - emit BridgingAccepted(address(receiver), address(token), amount); return true; } } diff --git a/ethereum/src/ERC20Treasury.sol b/ethereum/src/ERC20Treasury.sol index d9578c66..c98b2091 100644 --- a/ethereum/src/ERC20Treasury.sol +++ b/ethereum/src/ERC20Treasury.sol @@ -32,41 +32,39 @@ contract ERC20Treasury is IERC20Treasury, IMessageQueueReceiver { emit Deposit(tx.origin, to, token, amount); } - /** @dev Request withdraw of tokens. This request must be sent by `MessageQueue` only. Expected - * `payload` in `VaraMessage` consisits of these: + /** @dev Request withdraw of tokens. This request must be sent by `MessageQueue` only. + * + * Expected `payload` consisits of these: * - `receiver` - account to withdraw tokens to * - `token` - token to withdraw * - `amount` - amount of tokens to withdraw * - * @param vara_msg `VaraMessage` received from MessageQueue. + * Expected sender should be `vft-gateway` program on gear. + * + * @param sender sender of message on the gear side. + * @param payload payload of the message. */ function processVaraMessage( - VaraMessage calldata vara_msg + bytes32 sender, + bytes calldata payload ) external returns (bool) { - uint160 receiver; - uint160 token; - uint256 amount; if (msg.sender != MESSAGE_QUEUE_ADDRESS) { revert NotAuthorized(); } - - if (vara_msg.data.length != 20 + 20 + 32) { + if (payload.length != 20 + 20 + 32) { revert BadArguments(); } - if (vara_msg.receiver != address(this)) { - revert BadEthAddress(); - } - if (vara_msg.sender != VFT_GATEWAY_ADDRESS) { + if (sender != VFT_GATEWAY_ADDRESS) { revert BadVaraAddress(); } - assembly { - receiver := shr(96, calldataload(0xC4)) - token := shr(96, calldataload(0xD8)) - amount := calldataload(0xEC) - } - IERC20(address(token)).safeTransfer(address(receiver), amount); - emit Withdraw(address(receiver), address(token), amount); + address receiver = address(bytes20(payload[:20])); + address token = address(bytes20(payload[20:40])); + uint256 amount = uint256(bytes32(payload[40:])); + + IERC20(token).safeTransfer(receiver, amount); + emit Withdraw(receiver, token, amount); + return true; } } diff --git a/ethereum/src/MessageQueue.sol b/ethereum/src/MessageQueue.sol index 3e93e5b3..38e1d167 100644 --- a/ethereum/src/MessageQueue.sol +++ b/ethereum/src/MessageQueue.sol @@ -9,14 +9,13 @@ import {IRelayer} from "./interfaces/IRelayer.sol"; import {VaraMessage, VaraMessage, IMessageQueue, IMessageQueueReceiver, Hasher} from "./interfaces/IMessageQueue.sol"; import {MerkleProof} from "openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol"; - contract MessageQueue is IMessageQueue { using Address for address; using Hasher for VaraMessage; address immutable RELAYER_ADDRESS; - constructor (address relayer_address){ + constructor(address relayer_address) { RELAYER_ADDRESS = relayer_address; } @@ -64,18 +63,17 @@ contract MessageQueue is IMessageQueue { if (merkle_root == bytes32(0)) revert MerkleRootNotSet(block_number); if ( - _calculateMerkleRoot( - proof, - msg_hash, - total_leaves, - leaf_index - ) != merkle_root + _calculateMerkleRoot(proof, msg_hash, total_leaves, leaf_index) != + merkle_root ) revert BadProof(); _processed_messages[message.nonce] = true; if ( - !IMessageQueueReceiver(message.receiver).processVaraMessage(message) + !IMessageQueueReceiver(message.receiver).processVaraMessage( + message.sender, + message.data + ) ) { revert MessageNotProcessed(); } else { diff --git a/ethereum/src/ProxyContract.sol b/ethereum/src/ProxyContract.sol index ae9ff5fb..92b7e120 100644 --- a/ethereum/src/ProxyContract.sol +++ b/ethereum/src/ProxyContract.sol @@ -32,7 +32,6 @@ contract ProxyContract is Proxy { * * - If `data` is empty, `msg.value` must be zero. */ - function upgradeToAndCall( address newImplementation, bytes calldata data diff --git a/ethereum/src/ProxyUpdater.sol b/ethereum/src/ProxyUpdater.sol new file mode 100644 index 00000000..0b066c5a --- /dev/null +++ b/ethereum/src/ProxyUpdater.sol @@ -0,0 +1,82 @@ +pragma solidity ^0.8.24; + +import {IMessageQueueReceiver} from "./interfaces/IMessageQueue.sol"; +import {ProxyContract} from "./ProxyContract.sol"; + +contract ProxyUpdater is IMessageQueueReceiver { + error NotAuthorized(); + error NotGovernance(); + error BadArguments(); + error WrongDiscriminator(); + + ProxyContract proxy; + bytes32 governance; + address immutable MESSAGE_QUEUE_ADDRESS; + + constructor( + address payable _proxy, + bytes32 _governance, + address message_queue + ) payable { + proxy = ProxyContract(_proxy); + governance = _governance; + MESSAGE_QUEUE_ADDRESS = message_queue; + } + + /** @dev Accept request from MessageQueue. Based on the first byte of the payload + * make the decision what to do. + * + * If first byte = `0x00` then update implementation of underlying proxy. + * If first byte = `0x01` then change admin of the underlying proxy. + * If first byte = `0x02` then change governance. + * + * @param sender sender of message on the gear side. + * @param payload payload of the message. + */ + function processVaraMessage( + bytes32 sender, + bytes calldata payload + ) external returns (bool) { + if (msg.sender != MESSAGE_QUEUE_ADDRESS) { + revert NotAuthorized(); + } + if (sender != governance) { + revert NotGovernance(); + } + + uint8 discriminator = uint8(payload[0]); + + if (discriminator == 0x00) { + if (payload.length < 1 + 20) { + revert BadArguments(); + } + + address new_implementation = address(bytes20(payload[1:21])); + bytes calldata data = payload[21:]; + + proxy.upgradeToAndCall(new_implementation, data); + } else if (discriminator == 0x01) { + if (payload.length != 1 + 20) { + revert BadArguments(); + } + + address new_admin = address(bytes20(payload[1:])); + + proxy.changeProxyAdmin(new_admin); + } else if (discriminator == 0x02) { + if (payload.length != 1 + 32) { + revert BadArguments(); + } + + governance = bytes32(payload[1:]); + } else { + revert WrongDiscriminator(); + } + + return true; + } + + function getGovernance() external view returns (bytes32) { + return governance; + } +} diff --git a/ethereum/src/interfaces/IERC20Gateway.sol b/ethereum/src/interfaces/IERC20Gateway.sol index 2f581a21..fdb5e131 100644 --- a/ethereum/src/interfaces/IERC20Gateway.sol +++ b/ethereum/src/interfaces/IERC20Gateway.sol @@ -9,7 +9,6 @@ struct BridgingRequest { interface IERC20Gateway { error NotAuthorized(); error BadArguments(); - error BadEthAddress(); error BadVaraAddress(); event BridgingRequested( diff --git a/ethereum/src/interfaces/IERC20Treasury.sol b/ethereum/src/interfaces/IERC20Treasury.sol index 527c84a3..d9aa3e90 100644 --- a/ethereum/src/interfaces/IERC20Treasury.sol +++ b/ethereum/src/interfaces/IERC20Treasury.sol @@ -9,7 +9,6 @@ struct WithdrawMessage { interface IERC20Treasury { error NotAuthorized(); error BadArguments(); - error BadEthAddress(); error BadVaraAddress(); event Deposit( diff --git a/ethereum/src/interfaces/IMessageQueue.sol b/ethereum/src/interfaces/IMessageQueue.sol index 1989a9b1..729285f9 100644 --- a/ethereum/src/interfaces/IMessageQueue.sol +++ b/ethereum/src/interfaces/IMessageQueue.sol @@ -41,14 +41,13 @@ interface IMessageQueue { interface IMessageQueueReceiver { function processVaraMessage( - VaraMessage calldata vara_msg + bytes32 sender, + bytes calldata payload ) external returns (bool); } library Hasher { - function hash( - VaraMessage calldata message - ) public pure returns (bytes32) { + function hash(VaraMessage calldata message) public pure returns (bytes32) { bytes memory data = abi.encodePacked( message.nonce, message.sender, diff --git a/ethereum/test/ProxyUpdater.t.sol b/ethereum/test/ProxyUpdater.t.sol new file mode 100644 index 00000000..dbe7f5d4 --- /dev/null +++ b/ethereum/test/ProxyUpdater.t.sol @@ -0,0 +1,77 @@ +pragma solidity ^0.8.13; + +import {Test} from "forge-std/Test.sol"; +import {ProxyContract} from "../src/ProxyContract.sol"; +import {ProxyUpdater} from "../src/ProxyUpdater.sol"; + +contract Empty {} + +contract ProxyUpdaterTest is Test { + address constant MESSAGE_QUEUE = address(500); + address constant NEW_ADMIN = address(1000); + + address initialImpl; + address changedImpl; + + bytes32 constant GOVERNANCE = bytes32("governance_governance_governance"); + bytes32 constant NEW_GOVERNANCE = + bytes32("new_governance_governance_govern"); + + ProxyContract public proxy; + ProxyUpdater public updater; + + function setUp() public { + initialImpl = address(new Empty()); + changedImpl = address(new Empty()); + + proxy = new ProxyContract(); + proxy.upgradeToAndCall(initialImpl, ""); + + updater = new ProxyUpdater( + payable(address(proxy)), + GOVERNANCE, + MESSAGE_QUEUE + ); + + proxy.changeProxyAdmin(address(updater)); + } + + function test_updateImpl() public { + vm.startPrank(MESSAGE_QUEUE); + + assertEq(proxy.implementation(), initialImpl); + + updater.processVaraMessage( + GOVERNANCE, + abi.encodePacked(uint8(0), changedImpl, "") + ); + + assertEq(proxy.implementation(), changedImpl); + } + + function test_updateAdmin() public { + vm.startPrank(MESSAGE_QUEUE); + + assertEq(proxy.proxyAdmin(), address(updater)); + + updater.processVaraMessage( + GOVERNANCE, + abi.encodePacked(uint8(1), NEW_ADMIN) + ); + + assertEq(proxy.proxyAdmin(), NEW_ADMIN); + } + + function test_updateGovernance() public { + vm.startPrank(MESSAGE_QUEUE); + + assertEq(updater.getGovernance(), GOVERNANCE); + + updater.processVaraMessage( + GOVERNANCE, + abi.encodePacked(uint8(2), NEW_GOVERNANCE) + ); + + assertEq(updater.getGovernance(), NEW_GOVERNANCE); + } +} diff --git a/ethereum/test/Treasury.t.sol b/ethereum/test/Treasury.t.sol index 8623679e..9bcd6fe1 100644 --- a/ethereum/test/Treasury.t.sol +++ b/ethereum/test/Treasury.t.sol @@ -53,16 +53,12 @@ contract TreasuryTest is TestHelper { vm.expectRevert(); - VaraMessage memory vara_msg = VaraMessage({ - sender: VFT_GATEWAY_ADDRESS, - receiver: address(treasury), - nonce: bytes32(uint256(10)), - data: call_data - }); - - IMessageQueueReceiver(treasury).processVaraMessage(vara_msg); + IMessageQueueReceiver(treasury).processVaraMessage( + VFT_GATEWAY_ADDRESS, + call_data + ); vm.prank(address(message_queue)); - IMessageQueueReceiver(treasury).processVaraMessage(vara_msg); + IMessageQueueReceiver(treasury).processVaraMessage(VFT_GATEWAY_ADDRESS, call_data); } }