Skip to content

Commit

Permalink
feat(ethereum contracts): Introduce governance-driven ProxyUpdater (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
mertwole authored Nov 1, 2024
1 parent 2b8191a commit 0319cee
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 65 deletions.
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);
}

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

0 comments on commit 0319cee

Please sign in to comment.