-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
00be3d9
commit 514cd46
Showing
7 changed files
with
377 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
# @version 0.3.10 | ||
""" | ||
@title Gnosis Broadcaster | ||
@author CurveFi | ||
@notice Using Arbitrary Message Bridge (AMB) | ||
""" | ||
|
||
|
||
interface ArbitraryMessageBridge: | ||
def requireToPassMessage(_contract: address, _data: Bytes[(MAX_BYTES + 160) * MAX_MESSAGES], _gas: uint256) -> bytes32: nonpayable | ||
def maxGasPerTx() -> uint256: view | ||
|
||
|
||
event ApplyAdmins: | ||
admins: AdminSet | ||
|
||
event CommitAdmins: | ||
future_admins: AdminSet | ||
|
||
event SetBridge: | ||
bridge: ArbitraryMessageBridge | ||
|
||
|
||
enum Agent: | ||
OWNERSHIP | ||
PARAMETER | ||
EMERGENCY | ||
|
||
|
||
struct AdminSet: | ||
ownership: address | ||
parameter: address | ||
emergency: address | ||
|
||
struct Message: | ||
target: address | ||
data: Bytes[MAX_BYTES] | ||
|
||
|
||
MAX_BYTES: constant(uint256) = 1024 | ||
MAX_MESSAGES: constant(uint256) = 8 | ||
|
||
admins: public(AdminSet) | ||
future_admins: public(AdminSet) | ||
|
||
agent: HashMap[address, Agent] | ||
|
||
bridge: public(ArbitraryMessageBridge) | ||
|
||
|
||
@external | ||
def __init__(_admins: AdminSet, _bridge: ArbitraryMessageBridge): | ||
assert _admins.ownership != _admins.parameter # a != b | ||
assert _admins.ownership != _admins.emergency # a != c | ||
assert _admins.parameter != _admins.emergency # b != c | ||
|
||
self.admins = _admins | ||
|
||
self.agent[_admins.ownership] = Agent.OWNERSHIP | ||
self.agent[_admins.parameter] = Agent.PARAMETER | ||
self.agent[_admins.emergency] = Agent.EMERGENCY | ||
|
||
self.bridge = _bridge | ||
|
||
log ApplyAdmins(_admins) | ||
log SetBridge(_bridge) | ||
|
||
|
||
@external | ||
def broadcast(_messages: DynArray[Message, MAX_MESSAGES], _gas_limit: uint256=0): | ||
""" | ||
@notice Broadcast a sequence of messages. | ||
@param _messages The sequence of messages to broadcast. | ||
@param _gas_limit The L2 gas limit required to execute the sequence of messages. | ||
""" | ||
agent: Agent = self.agent[msg.sender] | ||
assert agent != empty(Agent) | ||
|
||
bridge: ArbitraryMessageBridge = self.bridge | ||
gas_limit: uint256 = _gas_limit if _gas_limit > 0 else bridge.maxGasPerTx() | ||
|
||
bridge.requireToPassMessage( | ||
self, | ||
_abi_encode( # relay(uint256,(address,bytes)[]) | ||
agent, | ||
_messages, | ||
method_id=method_id("relay(uint256,(address,bytes)[])"), | ||
), | ||
gas_limit, | ||
) | ||
|
||
|
||
@external | ||
def set_bridge(_bridge: ArbitraryMessageBridge): | ||
""" | ||
@notice Set ArbitraryMessageBridge contract proxy. | ||
""" | ||
assert msg.sender == self.admins.ownership | ||
|
||
self.bridge = _bridge | ||
log SetBridge(_bridge) | ||
|
||
|
||
@external | ||
def commit_admins(_future_admins: AdminSet): | ||
""" | ||
@notice Commit an admin set to use in the future. | ||
""" | ||
assert msg.sender == self.admins.ownership | ||
|
||
assert _future_admins.ownership != _future_admins.parameter # a != b | ||
assert _future_admins.ownership != _future_admins.emergency # a != c | ||
assert _future_admins.parameter != _future_admins.emergency # b != c | ||
|
||
self.future_admins = _future_admins | ||
log CommitAdmins(_future_admins) | ||
|
||
|
||
@external | ||
def apply_admins(): | ||
""" | ||
@notice Apply the future admin set. | ||
""" | ||
admins: AdminSet = self.admins | ||
assert msg.sender == admins.ownership | ||
|
||
# reset old admins | ||
self.agent[admins.ownership] = empty(Agent) | ||
self.agent[admins.parameter] = empty(Agent) | ||
self.agent[admins.emergency] = empty(Agent) | ||
|
||
# set new admins | ||
future_admins: AdminSet = self.future_admins | ||
self.agent[future_admins.ownership] = Agent.OWNERSHIP | ||
self.agent[future_admins.parameter] = Agent.PARAMETER | ||
self.agent[future_admins.emergency] = Agent.EMERGENCY | ||
|
||
self.admins = future_admins | ||
log ApplyAdmins(future_admins) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
# @version 0.3.10 | ||
""" | ||
@title Gnosis Relayer | ||
@author CurveFi | ||
""" | ||
|
||
|
||
interface IAgent: | ||
def execute(_messages: DynArray[Message, MAX_MESSAGES]): nonpayable | ||
|
||
|
||
enum Agent: | ||
OWNERSHIP | ||
PARAMETER | ||
EMERGENCY | ||
|
||
|
||
struct Message: | ||
target: address | ||
data: Bytes[MAX_BYTES] | ||
|
||
|
||
MAX_BYTES: constant(uint256) = 1024 | ||
MAX_MESSAGES: constant(uint256) = 8 | ||
|
||
CODE_OFFSET: constant(uint256) = 3 | ||
|
||
|
||
MESSENGER: public(immutable(address)) | ||
|
||
OWNERSHIP_AGENT: public(immutable(address)) | ||
PARAMETER_AGENT: public(immutable(address)) | ||
EMERGENCY_AGENT: public(immutable(address)) | ||
|
||
|
||
agent: HashMap[Agent, address] | ||
|
||
|
||
@external | ||
def __init__(_agent_blueprint: address, _messenger: address): | ||
MESSENGER = _messenger | ||
|
||
OWNERSHIP_AGENT = create_from_blueprint(_agent_blueprint, code_offset=CODE_OFFSET) | ||
PARAMETER_AGENT = create_from_blueprint(_agent_blueprint, code_offset=CODE_OFFSET) | ||
EMERGENCY_AGENT = create_from_blueprint(_agent_blueprint, code_offset=CODE_OFFSET) | ||
|
||
self.agent[Agent.OWNERSHIP] = OWNERSHIP_AGENT | ||
self.agent[Agent.PARAMETER] = PARAMETER_AGENT | ||
self.agent[Agent.EMERGENCY] = EMERGENCY_AGENT | ||
|
||
|
||
@external | ||
def relay(_agent: Agent, _messages: DynArray[Message, MAX_MESSAGES]): | ||
""" | ||
@notice Receive messages for an agent and relay them. | ||
@param _agent The agent to relay messages to. | ||
@param _messages The sequence of messages to relay. | ||
""" | ||
assert msg.sender == MESSENGER | ||
|
||
IAgent(self.agent[_agent]).execute(_messages) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# pragma version 0.3.10 | ||
|
||
|
||
MAX_LEN: constant(uint256) = 1024 | ||
|
||
contract: public(address) | ||
data: public(Bytes[MAX_LEN]) | ||
gas: public(uint256) | ||
|
||
|
||
@view | ||
@external | ||
def maxGasPerTx() -> uint256: | ||
return 4_000_000 | ||
|
||
|
||
@external | ||
def requireToPassMessage(_contract: address, _data: Bytes[MAX_LEN], _gas: uint256) -> bytes32: | ||
assert _gas >= 100 | ||
assert _gas <= 4_000_000 | ||
|
||
self.contract = _contract | ||
self.data = _data | ||
self.gas = _gas | ||
return convert(101, bytes32) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import click | ||
from ape import project | ||
from ape.cli import ConnectedProviderCommand, account_option, network_option | ||
|
||
|
||
@click.command(cls=ConnectedProviderCommand) | ||
@account_option() | ||
@network_option() | ||
@click.option("--blueprint") | ||
def cli(account, network, blueprint): | ||
chain_id = project.provider.chain_id | ||
|
||
if chain_id not in (1,): | ||
relayer = project.GnosisRelayer.deploy( | ||
blueprint, | ||
"0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59", | ||
gas_limit=800_000, | ||
gas_price=project.provider.gas_price, | ||
sender=account, | ||
) | ||
return project.Vault.deploy( | ||
relayer.OWNERSHIP_AGENT(), gas_price=project.provider.gas_price, sender=account | ||
) | ||
|
||
# L1 | ||
if chain_id == 1: | ||
admins = ( | ||
"0x40907540d8a6C65c637785e8f8B742ae6b0b9968", | ||
"0x4EEb3bA4f221cA16ed4A0cC7254E2E32DF948c5f", | ||
"0x467947EE34aF926cF1DCac093870f613C96B1E0c", | ||
) | ||
bridge = "0x4C36d2919e407f0Cc2Ee3c993ccF8ac26d9CE64e" | ||
|
||
return project.GnosisBroadcaster.deploy(admins, bridge, sender=account) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import pytest | ||
|
||
|
||
@pytest.fixture(scope="module") | ||
def mock_bridge(alice, project): | ||
yield project.MockGnosisBridge.deploy(sender=alice) | ||
|
||
|
||
@pytest.fixture(scope="module") | ||
def relayer(alice, project, agent_blueprint, mock_bridge): | ||
relayer = project.GnosisRelayer.deploy(agent_blueprint, mock_bridge, sender=alice) | ||
yield relayer | ||
|
||
|
||
@pytest.fixture(scope="module") | ||
def agents(relayer): | ||
yield [getattr(relayer, attr + "_AGENT")() for attr in ["OWNERSHIP", "PARAMETER", "EMERGENCY"]] | ||
|
||
|
||
@pytest.fixture(scope="module") | ||
def broadcaster(alice, bob, charlie, project, mock_bridge): | ||
yield project.GnosisBroadcaster.deploy((alice, bob, charlie), mock_bridge, sender=alice) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import itertools | ||
|
||
import ape | ||
import eth_abi | ||
import pytest | ||
from eth_utils import keccak | ||
|
||
|
||
def test_constructor(alice, bob, charlie, broadcaster, mock_bridge): | ||
assert broadcaster.admins() == (alice, bob, charlie) | ||
assert broadcaster.bridge() == mock_bridge | ||
|
||
|
||
@pytest.mark.parametrize("idx,gas", itertools.product(range(3), [0, 1_000])) | ||
def test_broadcast_success(alice, bob, charlie, broadcaster, mock_bridge, idx, gas): | ||
msg_sender = [alice, bob, charlie][idx] | ||
|
||
if gas > 0: | ||
broadcaster.broadcast([(alice.address, b"")], gas, sender=msg_sender) | ||
else: | ||
broadcaster.broadcast([(alice.address, b"")], sender=msg_sender) | ||
|
||
decoded = eth_abi.decode(["uint256", "(address,bytes)[]"], mock_bridge.data()[4:]) | ||
|
||
assert len(mock_bridge.data()) < 500 | ||
assert mock_bridge.data()[:4] == keccak(text="relay(uint256,(address,bytes)[])")[:4] | ||
assert decoded[0] == 2**idx | ||
assert decoded[1] == ((alice.address.lower(), b""),) | ||
|
||
assert mock_bridge.gas() == gas if gas > 0 else mock_bridge.maxGasPerTx() | ||
|
||
|
||
def test_broadcast_reverts(dave, broadcaster): | ||
with ape.reverts(): | ||
broadcaster.broadcast([(dave.address, b"")], sender=dave) | ||
|
||
|
||
def test_commit_admins(alice, bob, charlie, broadcaster): | ||
tx = broadcaster.commit_admins((alice, bob, charlie), sender=alice) | ||
|
||
assert broadcaster.future_admins() == (alice, bob, charlie) | ||
assert len(tx.logs) == 1 | ||
assert tx.logs[0]["topics"][0] == keccak("CommitAdmins((address,address,address))".encode()) | ||
|
||
with ape.reverts(): | ||
broadcaster.commit_admins((bob, charlie, alice), sender=bob) | ||
|
||
with ape.reverts(): | ||
broadcaster.commit_admins((alice, alice, alice), sender=alice) | ||
|
||
|
||
def test_apply_admins(alice, bob, charlie, broadcaster): | ||
broadcaster.commit_admins((charlie, bob, alice), sender=alice) | ||
|
||
with ape.reverts(): | ||
broadcaster.apply_admins(sender=bob) | ||
|
||
tx = broadcaster.apply_admins(sender=alice) | ||
|
||
assert broadcaster.admins() == (charlie, bob, alice) | ||
assert len(tx.logs) == 1 | ||
assert tx.logs[0]["topics"][0] == keccak("ApplyAdmins((address,address,address))".encode()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import math | ||
|
||
import ape | ||
import pytest | ||
|
||
from tests import AgentEnum | ||
|
||
|
||
def test_constructor(alice, project, agent_blueprint, mock_bridge, ZERO_ADDRESS): | ||
relayer = project.OptimismRelayer.deploy(agent_blueprint, mock_bridge, sender=alice) | ||
|
||
assert relayer.OWNERSHIP_AGENT() != ZERO_ADDRESS | ||
assert relayer.PARAMETER_AGENT() != ZERO_ADDRESS | ||
assert relayer.EMERGENCY_AGENT() != ZERO_ADDRESS | ||
assert relayer.MESSENGER() == mock_bridge | ||
|
||
|
||
@pytest.mark.parametrize("agent", AgentEnum) | ||
def test_relay_success(alice, relayer, mock_bridge, agent, agents): | ||
agent_addr = agents[int(math.log2(agent))] | ||
tx = relayer.relay(agent, [(alice.address, b"")], sender=mock_bridge) | ||
|
||
targets = [f.contract_address for f in tx.trace if f.op == "CALL"] | ||
assert {agent_addr, alice.address} == set(targets) | ||
|
||
|
||
def test_relay_invalid_caller(alice, relayer): | ||
with ape.reverts(): | ||
relayer.relay(AgentEnum.OWNERSHIP, [(alice.address, b"")], sender=alice) | ||
|
||
|
||
def test_relay_invalid_agent(alice, relayer, mock_bridge): | ||
with ape.reverts(): | ||
relayer.relay(42, [(alice.address, b"")], sender=mock_bridge) |