Skip to content

Commit d176d47

Browse files
committed
feat: add taiko xgov implementation
1 parent a2e7655 commit d176d47

File tree

5 files changed

+409
-3
lines changed

5 files changed

+409
-3
lines changed

contracts/Agent.vy

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,38 @@
1-
# pragma version 0.3.10
1+
# pragma version 0.4.0
2+
# pragma evm-version shanghai
23
"""
34
@title Agent
45
@author CurveFi
6+
@license MIT
7+
@custom:version 1.0.0
58
"""
69

10+
version: public(constant(String[8])) = "1.0.0"
11+
12+
interface IAgent:
13+
def execute(_messages: DynArray[Message, MAX_MESSAGES]): nonpayable
14+
def RELAYER() -> address: view
15+
716

817
struct Message:
918
target: address
1019
data: Bytes[MAX_BYTES]
1120

1221

22+
flag Agent:
23+
OWNERSHIP
24+
PARAMETER
25+
EMERGENCY
26+
27+
1328
MAX_BYTES: constant(uint256) = 1024
1429
MAX_MESSAGES: constant(uint256) = 8
1530

1631

1732
RELAYER: public(immutable(address))
1833

1934

20-
@external
35+
@deploy
2136
def __init__():
2237
RELAYER = msg.sender
2338

@@ -30,5 +45,5 @@ def execute(_messages: DynArray[Message, MAX_MESSAGES]):
3045
"""
3146
assert msg.sender == RELAYER
3247

33-
for message in _messages:
48+
for message: Message in _messages:
3449
raw_call(message.target, message.data)

contracts/Broadcaster.vy

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# pragma version 0.4.0
2+
"""
3+
@title Broadcaster
4+
@author Curve.Fi
5+
@license Copyright (c) Curve.Fi, 2020-2024 - all rights reserved
6+
@custom:version 0.0.1
7+
@notice Governance message broadcaster
8+
"""
9+
10+
import Agent as agent_lib
11+
12+
13+
event Broadcast:
14+
chain_id: indexed(uint256)
15+
agent: indexed(agent_lib.Agent)
16+
messages: DynArray[agent_lib.Message, agent_lib.MAX_MESSAGES]
17+
18+
19+
event ApplyAdmins:
20+
admins: AdminSet
21+
22+
event CommitAdmins:
23+
future_admins: AdminSet
24+
25+
26+
struct AdminSet:
27+
ownership: address
28+
parameter: address
29+
emergency: address
30+
31+
32+
admins: public(AdminSet)
33+
future_admins: public(AdminSet)
34+
35+
agent: public(HashMap[address, agent_lib.Agent])
36+
37+
38+
39+
@deploy
40+
def __init__(_admins: AdminSet):
41+
assert _admins.ownership != _admins.parameter # a != b
42+
assert _admins.ownership != _admins.emergency # a != c
43+
assert _admins.parameter != _admins.emergency # b != c
44+
45+
self.admins = _admins
46+
47+
self.agent[_admins.ownership] = agent_lib.Agent.OWNERSHIP
48+
self.agent[_admins.parameter] = agent_lib.Agent.PARAMETER
49+
self.agent[_admins.emergency] = agent_lib.Agent.EMERGENCY
50+
51+
log ApplyAdmins(_admins)
52+
53+
54+
@internal
55+
def _broadcast_check(chain_id: uint256, messages: DynArray[agent_lib.Message, agent_lib.MAX_MESSAGES]) -> agent_lib.Agent:
56+
"""
57+
@notice Broadcast a sequence of messages.
58+
@param _chain_id Chain ID of L2
59+
@param _messages The sequence of messages to broadcast.
60+
"""
61+
agent: agent_lib.Agent = self.agent[msg.sender]
62+
assert agent != empty(agent_lib.Agent)
63+
log Broadcast(chain_id, agent, messages)
64+
65+
return agent
66+
67+
68+
@external
69+
def commit_admins(_future_admins: AdminSet):
70+
"""
71+
@notice Commit an admin set to use in the future.
72+
"""
73+
assert msg.sender == self.admins.ownership
74+
75+
assert _future_admins.ownership != _future_admins.parameter # a != b
76+
assert _future_admins.ownership != _future_admins.emergency # a != c
77+
assert _future_admins.parameter != _future_admins.emergency # b != c
78+
79+
self.future_admins = _future_admins
80+
log CommitAdmins(_future_admins)
81+
82+
83+
@external
84+
def apply_admins():
85+
"""
86+
@notice Apply the future admin set.
87+
"""
88+
admins: AdminSet = self.admins
89+
assert msg.sender == admins.ownership
90+
91+
# reset old admins
92+
self.agent[admins.ownership] = empty(agent_lib.Agent)
93+
self.agent[admins.parameter] = empty(agent_lib.Agent)
94+
self.agent[admins.emergency] = empty(agent_lib.Agent)
95+
96+
# set new admins
97+
future_admins: AdminSet = self.future_admins
98+
self.agent[future_admins.ownership] = agent_lib.Agent.OWNERSHIP
99+
self.agent[future_admins.parameter] = agent_lib.Agent.PARAMETER
100+
self.agent[future_admins.emergency] = agent_lib.Agent.EMERGENCY
101+
102+
self.admins = future_admins
103+
log ApplyAdmins(future_admins)

contracts/Relayer.vy

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# pragma version 0.4.0
2+
"""
3+
@title Relayer
4+
@author Curve.Fi
5+
@license Copyright (c) Curve.Fi, 2020-2024 - all rights reserved
6+
@custom:version 0.0.1
7+
@notice Governance message relayer
8+
"""
9+
10+
import Agent as agent_lib
11+
12+
event Relay:
13+
agent: agent_lib.Agent
14+
messages: DynArray[agent_lib.Message, agent_lib.MAX_MESSAGES]
15+
16+
17+
18+
MAX_BYTES: constant(uint256) = 1024
19+
MAX_MESSAGES: constant(uint256) = 8
20+
21+
CODE_OFFSET: constant(uint256) = 3
22+
23+
24+
OWNERSHIP_AGENT: public(immutable(address))
25+
PARAMETER_AGENT: public(immutable(address))
26+
EMERGENCY_AGENT: public(immutable(address))
27+
28+
29+
agent: HashMap[agent_lib.Agent, agent_lib.IAgent]
30+
31+
BROADCASTER: public(immutable(address))
32+
33+
34+
@deploy
35+
def __init__(_broadcaster: address, _agent_blueprint: address):
36+
BROADCASTER = _broadcaster
37+
38+
OWNERSHIP_AGENT = create_from_blueprint(_agent_blueprint, code_offset=CODE_OFFSET)
39+
PARAMETER_AGENT = create_from_blueprint(_agent_blueprint, code_offset=CODE_OFFSET)
40+
EMERGENCY_AGENT = create_from_blueprint(_agent_blueprint, code_offset=CODE_OFFSET)
41+
42+
self.agent[agent_lib.Agent.OWNERSHIP] = agent_lib.IAgent(OWNERSHIP_AGENT)
43+
self.agent[agent_lib.Agent.PARAMETER] = agent_lib.IAgent(PARAMETER_AGENT)
44+
self.agent[agent_lib.Agent.EMERGENCY] = agent_lib.IAgent(EMERGENCY_AGENT)
45+
46+
47+
@internal
48+
def _relay(_agent: agent_lib.Agent, _messages: DynArray[agent_lib.Message, agent_lib.MAX_MESSAGES]):
49+
"""
50+
@notice Receive messages for an agent and relay them.
51+
@param _agent The agent to relay messages to.
52+
@param _messages The sequence of messages to relay.
53+
"""
54+
extcall self.agent[_agent].execute(_messages)
55+
56+
log Relay(_agent, _messages)

contracts/taiko/TaikoBroadcaster.vy

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# pragma version 0.4.0
2+
"""
3+
@title CurveXGovTaikoBroadcaster
4+
@author Curve.Fi
5+
@license Copyright (c) Curve.Fi, 2020-2024 - all rights reserved
6+
@custom:version 0.0.1
7+
@notice Taiko governance message broadcaster
8+
"""
9+
10+
version: public(constant(String[8])) = "0.0.1"
11+
12+
import contracts.Broadcaster as Broadcaster
13+
14+
initializes: Broadcaster
15+
16+
17+
interface Bridge:
18+
def sendMessage(_message: Message) -> (bytes32, Message): payable
19+
20+
21+
event SetDestinationData:
22+
chain_id: indexed(uint256)
23+
destination_data: DestinationData
24+
25+
26+
struct Message:
27+
id: uint64 # Message ID whose value is automatically assigned.
28+
fee: uint64 # The max processing fee for the relayer. This fee has 3 parts:
29+
# - the fee for message calldata.
30+
# - the minimal fee reserve for general processing, excluding function call.
31+
# - the invocation fee for the function call.
32+
# Any unpaid fee will be refunded to the destOwner on the destination chain.
33+
# Note that fee must be 0 if gasLimit is 0, or large enough to make the invocation fee
34+
# non-zero.
35+
gasLimit: uint32 # gasLimit that the processMessage call must have.
36+
_from: address # The address, EOA or contract, that interacts with this bridge.
37+
# The value is automatically assigned.
38+
srcChainId: uint64 # Source chain ID whose value is automatically assigned.
39+
srcOwner: address # The owner of the message on the source chain.
40+
destChainId: uint64 # Destination chain ID where the `to` address lives.
41+
destOwner: address # The owner of the message on the destination chain.
42+
to: address # The destination address on the destination chain.
43+
value: uint256 # value to invoke on the destination chain.
44+
data: Bytes[MAX_MESSAGE_RECEIVED] # callData to invoke on the destination chain.
45+
46+
47+
struct DestinationData:
48+
gas_price: uint256
49+
gas_limit: uint256
50+
dest_owner: address # FeeCollector or Curve Vault
51+
relayer: address
52+
allow_manual_parameters: bool
53+
54+
struct ManualParameters:
55+
dest_owner: address
56+
57+
58+
MAX_MESSAGE_RECEIVED: constant(uint256) = 9400
59+
60+
BRIDGE: public(constant(Bridge)) = Bridge(0xd60247c6848B7Ca29eDdF63AA924E53dB6Ddd8EC)
61+
62+
destination_data: public(HashMap[uint256, DestinationData])
63+
64+
manual_parameters: transient(ManualParameters)
65+
66+
67+
@deploy
68+
def __init__(_admins: Broadcaster.AdminSet):
69+
Broadcaster.__init__(_admins)
70+
71+
exports: Broadcaster.__interface__
72+
73+
74+
@external
75+
def __default__():
76+
pass
77+
78+
79+
@internal
80+
def _applied_destination_data(data: DestinationData) -> DestinationData:
81+
"""
82+
@notice Apply manual parameters
83+
"""
84+
if data.allow_manual_parameters:
85+
dest_owner: address = self.manual_parameters.dest_owner
86+
if dest_owner != empty(address):
87+
data.dest_owner = dest_owner
88+
89+
return data
90+
91+
92+
@payable
93+
@external
94+
def broadcast(_chain_id: uint256, _messages: DynArray[Broadcaster.agent_lib.Message, Broadcaster.agent_lib.MAX_MESSAGES], _destination_data: DestinationData=empty(DestinationData)):
95+
"""
96+
@notice Broadcast a sequence of messages.
97+
@dev Save `depositCount` from POLYGON_ZKEVM_BRIDGE.BridgeEvent to claim message on destination chain
98+
@param _chain_id Chain ID of L2
99+
@param _messages The sequence of messages to broadcast.
100+
@param _destination_data Specific DestinationData (self.destination_data by default)
101+
"""
102+
agent: Broadcaster.agent_lib.Agent = Broadcaster._broadcast_check(_chain_id, _messages)
103+
104+
destination: DestinationData = _destination_data
105+
if destination.relayer == empty(address):
106+
destination = self.destination_data[_chain_id]
107+
assert destination.relayer != empty(address)
108+
109+
data: DestinationData = self._applied_destination_data(destination)
110+
111+
fee: uint256 = data.gas_price * data.gas_limit
112+
extcall BRIDGE.sendMessage(
113+
Message(
114+
id=0, # Message ID whose value is automatically assigned.
115+
fee=convert(fee, uint64),
116+
gasLimit=convert(data.gas_limit, uint32),
117+
_from=empty(address), # The value is automatically assigned.
118+
srcChainId=0, # Source chain ID whose value is automatically assigned.
119+
srcOwner=msg.sender,
120+
destChainId=convert(_chain_id, uint64),
121+
destOwner=data.dest_owner,
122+
to=data.relayer,
123+
value=0,
124+
data=abi_encode(
125+
abi_encode( # relay(uint256,(address,bytes)[])
126+
agent,
127+
_messages,
128+
),
129+
method_id=method_id("onMessageInvocation(bytes)")
130+
), # callData to invoke on the destination chain.
131+
),
132+
value=fee,
133+
)
134+
135+
136+
@view
137+
@external
138+
def cost(_chain_id: uint256) -> uint256:
139+
"""
140+
@notice Cost in ETH to bridge
141+
"""
142+
data: DestinationData = self.destination_data[_chain_id]
143+
return data.gas_price * data.gas_limit
144+
145+
146+
@external
147+
def set_manual_parameters(_manual_parameters: ManualParameters):
148+
"""
149+
@notice Set manual parameters that will be actual within current transaction
150+
"""
151+
self.manual_parameters = _manual_parameters
152+
153+
154+
@external
155+
def set_destination_data(_chain_id: uint256, _destination_data: DestinationData):
156+
"""
157+
@notice Set custom destination data. In order to turn off chain id set bridge=0xdead
158+
"""
159+
assert msg.sender == Broadcaster.admins.ownership
160+
self.destination_data[_chain_id] = _destination_data
161+
log SetDestinationData(_chain_id, _destination_data)

0 commit comments

Comments
 (0)