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

feat(ethereum contracts): Introduce governance-driven ProxyUpdater #179

Merged
merged 6 commits into from
Nov 1, 2024
Merged
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
36 changes: 16 additions & 20 deletions ethereum/src/ERC20Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
38 changes: 18 additions & 20 deletions ethereum/src/ERC20Treasury.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,41 +32,39 @@ contract ERC20Treasury is IERC20Treasury, IMessageQueueReceiver {
emit Deposit(tx.origin, to, token, amount);
mertwole marked this conversation as resolved.
Show resolved Hide resolved
}

/** @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;
}
}
Expand Down
16 changes: 7 additions & 9 deletions ethereum/src/MessageQueue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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 {
Expand Down
1 change: 0 additions & 1 deletion ethereum/src/ProxyContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ contract ProxyContract is Proxy {
*
* - If `data` is empty, `msg.value` must be zero.
*/

function upgradeToAndCall(
address newImplementation,
bytes calldata data
Expand Down
82 changes: 82 additions & 0 deletions ethereum/src/ProxyUpdater.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
1 change: 0 additions & 1 deletion ethereum/src/interfaces/IERC20Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ struct BridgingRequest {
interface IERC20Gateway {
error NotAuthorized();
error BadArguments();
error BadEthAddress();
error BadVaraAddress();

event BridgingRequested(
Expand Down
1 change: 0 additions & 1 deletion ethereum/src/interfaces/IERC20Treasury.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ struct WithdrawMessage {
interface IERC20Treasury {
error NotAuthorized();
error BadArguments();
error BadEthAddress();
error BadVaraAddress();

event Deposit(
Expand Down
7 changes: 3 additions & 4 deletions ethereum/src/interfaces/IMessageQueue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
77 changes: 77 additions & 0 deletions ethereum/test/ProxyUpdater.t.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
14 changes: 5 additions & 9 deletions ethereum/test/Treasury.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Loading