-
Notifications
You must be signed in to change notification settings - Fork 492
/
token_bridge.cairo
171 lines (146 loc) · 6.11 KB
/
token_bridge.cairo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
use starknet::ContractAddress;
#[starknet::interface]
trait IMintableToken<T> {
fn permissioned_mint(ref self: T, account: ContractAddress, amount: u256);
fn permissioned_burn(ref self: T, account: ContractAddress, amount: u256);
}
#[starknet::contract]
mod token_bridge {
use core::num::traits::Zero;
use starknet::SyscallResultTrait;
use starknet::{
ContractAddress, get_caller_address, EthAddress, syscalls::send_message_to_l1_syscall
};
use super::{
IMintableTokenDispatcher, IMintableTokenLibraryDispatcher, IMintableTokenDispatcherTrait
};
const WITHDRAW_MESSAGE: felt252 = 0;
const CONTRACT_IDENTITY: felt252 = 'STARKGATE';
const CONTRACT_VERSION: felt252 = 2;
#[storage]
struct Storage {
// The address of the L2 governor of this contract. Only the governor can set the other
// storage variables.
governor: ContractAddress,
// The L1 bridge address. Zero when unset.
l1_bridge: felt252,
// The L2 token contract address. Zero when unset.
l2_token: ContractAddress,
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
L1BridgeSet: L1BridgeSet,
L2TokenSet: L2TokenSet,
WithdrawInitiated: WithdrawInitiated,
DepositHandled: DepositHandled,
}
// An event that is emitted when set_l1_bridge is called.
// * l1_bridge_address is the new l1 bridge address.
#[derive(Drop, starknet::Event)]
struct L1BridgeSet {
l1_bridge_address: EthAddress,
}
// An event that is emitted when set_l2_token is called.
// * l2_token_address is the new l2 token address.
#[derive(Drop, starknet::Event)]
struct L2TokenSet {
l2_token_address: ContractAddress,
}
// An event that is emitted when initiate_withdraw is called.
// * l1_recipient is the l1 recipient address.
// * amount is the amount to withdraw.
// * caller_address is the address from which the call was made.
#[derive(Drop, starknet::Event)]
struct WithdrawInitiated {
l1_recipient: EthAddress,
amount: u256,
caller_address: ContractAddress,
}
// An event that is emitted when handle_deposit is called.
// * account is the recipient address.
// * amount is the amount to deposit.
#[derive(Drop, starknet::Event)]
struct DepositHandled {
account: ContractAddress,
amount: u256,
}
#[constructor]
fn constructor(ref self: ContractState, governor_address: ContractAddress) {
assert(governor_address.is_non_zero(), 'ZERO_GOVERNOR_ADDRESS');
self.governor.write(governor_address);
}
#[generate_trait]
#[abi(per_item)]
impl TokenBridgeImpl of ITokenBridge {
// TODO(spapini): Consider adding a pure option, with no parameters.
#[external(v0)]
fn get_version(self: @ContractState) -> felt252 {
CONTRACT_VERSION
}
#[external(v0)]
fn get_identity(self: @ContractState) -> felt252 {
CONTRACT_IDENTITY
}
#[external(v0)]
fn set_l1_bridge(ref self: ContractState, l1_bridge_address: EthAddress) {
// The call is restricted to the governor.
assert(get_caller_address() == self.governor.read(), 'GOVERNOR_ONLY');
assert(self.l1_bridge.read().is_zero(), 'L1_BRIDGE_ALREADY_INITIALIZED');
assert(l1_bridge_address.is_non_zero(), 'ZERO_BRIDGE_ADDRESS');
self.l1_bridge.write(l1_bridge_address.into());
self.emit(L1BridgeSet { l1_bridge_address });
}
#[external(v0)]
fn set_l2_token(ref self: ContractState, l2_token_address: ContractAddress) {
// The call is restricted to the governor.
assert(get_caller_address() == self.governor.read(), 'GOVERNOR_ONLY');
assert(self.l2_token.read().is_zero(), 'L2_TOKEN_ALREADY_INITIALIZED');
assert(l2_token_address.is_non_zero(), 'ZERO_TOKEN_ADDRESS');
self.l2_token.write(l2_token_address);
self.emit(L2TokenSet { l2_token_address });
}
#[external(v0)]
fn initiate_withdraw(ref self: ContractState, l1_recipient: EthAddress, amount: u256) {
// Call burn on l2_token contract.
let caller_address = get_caller_address();
IMintableTokenDispatcher { contract_address: self.read_initialized_l2_token() }
.permissioned_burn(account: caller_address, :amount);
// Send the message.
let mut message_payload: Array<felt252> = array![
WITHDRAW_MESSAGE, l1_recipient.into(), amount.low.into(), amount.high.into()
];
send_message_to_l1_syscall(
to_address: self.read_initialized_l1_bridge(), payload: message_payload.span()
)
.unwrap_syscall();
self.emit(WithdrawInitiated { l1_recipient, amount, caller_address });
}
}
#[l1_handler]
fn handle_deposit(
ref self: ContractState, from_address: felt252, account: ContractAddress, amount: u256
) {
assert(from_address == self.l1_bridge.read(), 'EXPECTED_FROM_BRIDGE_ONLY');
// Call mint on l2_token contract.
IMintableTokenDispatcher { contract_address: self.read_initialized_l2_token() }
.permissioned_mint(:account, :amount);
self.emit(Event::DepositHandled(DepositHandled { account, amount }));
}
/// Helpers (internal functions)
#[generate_trait]
impl HelperImpl of HelperTrait {
// Read l1_bridge and verify it's initialized.
fn read_initialized_l1_bridge(self: @ContractState) -> felt252 {
let l1_bridge_address = self.l1_bridge.read();
assert(l1_bridge_address.is_non_zero(), 'UNINITIALIZED_L1_BRIDGE_ADDRESS');
l1_bridge_address
}
// Read l2_token and verify it's initialized.
fn read_initialized_l2_token(self: @ContractState) -> ContractAddress {
let l2_token_address = self.l2_token.read();
assert(l2_token_address.is_non_zero(), 'UNINITIALIZED_TOKEN');
l2_token_address
}
}
}