<100 subscribers
Share Dialog
Share Dialog
Starknet: L2与 L1的合约交互
原文: https://starknet.io/docs/hello_starknet/l1l2.html
翻译: lei.ju 鞠磊
一个好的 L2 系统要能够与它的 L1 系统进行交互(否则成了孤立系统)。本节中我们将描述 StarkNet 合约如何与以太坊 L1 合约进行交互。
每个 StarkNet 合约都可以向任何 L1 合约发送和接收消息。通常,建议设计一对合约:一个 L2 合约及其对应的 L1 合约(如 Solidity ),并设计好两个合约之间的消息协议。
从 L2 到 L1 的消息工作如下:
StarkNet (L2) 合约函数调用库函数 send_message_to_l1() 以发送消息。它规定:
a. 目的地 L1 合约(“to”),
b. 要发送的数据 Payload
StarkNet OS 会自动添加“From”地址,即发送消息的合约的 L2 地址
一旦包含 L2 交易的状态更新在链上被接受,消息就会存储在 L1 StarkNet 核心合约中,等待被消费。
“to”地址指定的 L1 合约调用 StarkNet 核心合约的 consumeMessageFromL2()
注意:由于任何 L2 合约都可以向任何 L1 合约发送消息,因此建议 L1 合约在处理交易之前检查“From”地址。
后面我们会展示如何使用这种机制来实现L2->L1的withdraw。
另一个方向类似地:
L1 合约(在 L1 链上)调用StarkNet 核心合约的 send_message() 函数,该函数会存储这个消息。消息中包含一个附加字段——“selector”,指示了相应的 L2 合约中应调用什么函数。
StarkNet Sequencer会自动消费消息,调用由L2合约的function。 目标L2合约由“to”地址指定,目标函数由selector字段指定。
这个方向的消息可用于L1->L2的“Deposit”
注意,虽然诚实的Starknet Sequencer会自动消费所有 L1 -> L2 消息,但Startnet协议并未强制这一点. 因此 Sequencer有可能会选择跳过消息。在设计两个合约之间的消息协议时应该考虑到这一点。
重要提示:StarkNet Alpha 系统仍在开发中,因此系统状态会不时重置并删除所有合约。这意味着你不应该将有价值的资产转移到 StarkNet 系统,除非你确信能把资产withdraw回L1。That being said, recall that StarkNet Alpha only runs on the Goerli testnet.
在本节中,我们将构建一个简单的跨链桥——用户将能够存入 L2代币,他们的L2 余额将会增加。 然后,从L2转一些代币到L1,这将减少 L2 帐户余额并相应增加 L1帐户余额。
%lang starknet
from starkware.cairo.common.alloc import alloc
from starkware.cairo.common.cairo_builtins import HashBuiltin
from starkware.cairo.common.math import assert_nn
from starkware.starknet.common.**messages import send_message_to_l1
const L1_CONTRACT_ADDRESS = (0x2Db8c2615db39a5eD8750B87aC8F217485BE11EC)
const MESSAGE_WITHDRAW = 0
注意,在实际应用中,可能会使用@storage_var存储L1 Contract Address,而不是使用一个Constant.
@storage_var
func balance(user : felt) -> (res : felt):
end
@view
func get_balance {
syscall_ptr : felt*
pedersen_ptr : HashBuiltin*,
range_check_ptr,
}(user : felt) -> (balance : felt):
let (res) = balance.read(user=user)
return (res)
end
这样我们能有一些“资金”可以玩。下面定义了一个可以铸造新代币的函数。在实际应用中,你可能不想要一个让用户直接印钱的函数。此外,需要检查"amoun"t参数是否为负数。
// --------印钱 -------
@external
func increase_balance{
syscall_ptr : felt*,
pedersen_ptr : HashBuiltin*,
range_check_ptr,
}(user : felt, amount : felt):
let (res) = balance.read(user=user)
balance.write(user, res + amount)
return ()
end
L2->L1消息可用于从L2向L1转账(widthdraw):
请求提款(withdraw)的用户发起一个L2 withdraw 交易
这个交易(transaction)会减少L2 帐号余额,并向 L1 合约发送消息,要求将用户的 L1 余额增加相应金额。
跨链桥L1侧合约会消费这个消息,并按消息中的请求, 增加帐户 L1 余额。
@external
func withdraw {
syscall_ptr : felt*,
pedersen_ptr : HashBuiltin*,
range_check_ptr,
}(user : felt, amount : felt):
# Make sure 'amount' is positive.
assert_nn(amount)
let (res) = balance.read(user=user)
tempvar new_balance = res - amount
# Make sure the new balance will be positive.
assert_nn(new_balance)
# Update the new balance.
balance.write(user, new_balance)
# Send the withdrawal message.
let (message_payload : felt*) = alloc()
assert message_payload[0] = MESSAGE_WITHDRAW
assert message_payload[1] = user
assert message_payload[2] = amount
send_message_to_l1( to_address=L1_CONTRACT_ADDRESS,
payload_size=3,
payload=message_payload,
)
return ()
请注意,添加了一个新的隐式参数@syscall_ptr, 这个参数允许我们调用 StarkNet OS的一些函数,如send_message_to_l1()
通过调用 send_message_to_l1() 发送消息. 这个方法需要获取 L1 合约地址、消息大小和消息本身(类型为”felt“)。请注意,消息本身是作为指针给出的,因此必须显式传递消息长度。在我们的示例中,消息数据为:MESSAGE_WITHDRAW、用户、金额。我们选择使用第一个元素作为消息类型的指示符(请注意,我们在这里并不真正需要它,因为我们只有一个消息类型)。
至于如何编写跨链桥的 L1侧合约, 可参考: https://starknet.io/docs/_static/L1L2Example.sol 。可以研究一下其中的withdraw()函数:它获取用户和金额,消费消息(如果没有在链上收到消息,这部分将失败)并相应地更新用户的余额。正如下面代码所示,我们将 L2 合约的地址作为参数传递给函数,这样这个L1合约一旦部署后,本教程的学习者都可以直接调用它。但是实际应用中, 用 L2 合约的地址作为参数通常是没有意义的——实际应用中地址应该是固定的。
为了处理从 L1 合约发送的消息,应声明一个@l1_handler
@l1_handler
func deposit{
syscall_ptr : felt*,
pedersen_ptr : HashBuiltin*,
range_check_ptr,
}(from_address : felt, user : felt, amount : felt):
# Make sure the message was sent by the intended L1 contract.
assert from_address = L1_CONTRACT_ADDRESS
# Read the current balance.
let (res) = balance.read(user=user)
# Compute and update the new balance.
tempvar new_balance = res + amount
balance.write(user, new_balance)
return ()
end
Starknet OS会调用Handler处理L1的message。一个合约可以定义多个Handler。Handler由一个称为Selector的整数值标识。可以使用下面的Python代码计算一个Handler的Selector值。
from starkware.starknet.compiler.compile import \
get_selector_from_name
print**(get_selector_from_name('deposit'))
运行结果:
当 L1 合约想要发送消息时,它会调用 StarkNet Core 合约的 sendMessageToL2() 函数,并指定 L2 合约地址和要调用的处理程序的selector。 请参考https://starknet.io/docs/_static/L1L2Example.sol中的function deposit。
将上述代码保存至文件l 1l2.cairo, 或点击这里下载: here
$ starknet-compile l1l2.cairo \
--output l1l2_compiled.json \
--abi l1l2_abi.json
$ starknet deploy --contract l1l2_compiled.json
# The deployment address of the previous contract.
export CONTRACT_ADDRESS="<address of the previous contract>"
export USERID="<favorite 251-bit integer>"
$ starknet invoke \
--address ${CONTRACT_ADDRESS} \
--abi l1l2_abi.json \
--function increase_balance \
--inputs \
${USERID} 3333
调用合约的withdraw() 函数,使用以下参数:
CONTRACT_ADDRESS, USERID, 1000
和以前一样CONTRACT_ADDRESS 是 L2 合约的地址 (见上文export的环境变量)。
$ starknet invoke \
--address ${CONTRACT_ADDRESS} \
--abi l1l2_abi.json \
--function withdraw \
--inputs \
${USERID} 1000
要等待交易被区块链接受(这可能需要一些时间)。 期间您可以使用 starknet tx_status 来跟踪交易的进度。
检查结果: 应为 3333 - 1000 = 2333块.
在withdraw()交易之后,用户余额应为:
L1 余额 : 1000, L2 余额 : 2333
$. starknet call \
--address ${CONTRACT_ADDRESS} \
--abi l1l2_abi.json \
--function get_balance \
--inputs \
${USERID}
结果:
2333
调用合约的 deposit() 函数, 从L1转账600块到L2。
使用以下参数:
CONTRACT_ADDRESS、USERID、600
StarkNet 可能需要一些时间来处理传入消息并调用 L1 Handler(例如,系统等待一些区块链确认)。 但在那之后,您将能够通过再次调用 get_balance 的 starknet 来查看用户的更新余额。
新余额应为
L1 余额 : 400, L2 余额 : 2933。
Starknet: L2与 L1的合约交互
原文: https://starknet.io/docs/hello_starknet/l1l2.html
翻译: lei.ju 鞠磊
一个好的 L2 系统要能够与它的 L1 系统进行交互(否则成了孤立系统)。本节中我们将描述 StarkNet 合约如何与以太坊 L1 合约进行交互。
每个 StarkNet 合约都可以向任何 L1 合约发送和接收消息。通常,建议设计一对合约:一个 L2 合约及其对应的 L1 合约(如 Solidity ),并设计好两个合约之间的消息协议。
从 L2 到 L1 的消息工作如下:
StarkNet (L2) 合约函数调用库函数 send_message_to_l1() 以发送消息。它规定:
a. 目的地 L1 合约(“to”),
b. 要发送的数据 Payload
StarkNet OS 会自动添加“From”地址,即发送消息的合约的 L2 地址
一旦包含 L2 交易的状态更新在链上被接受,消息就会存储在 L1 StarkNet 核心合约中,等待被消费。
“to”地址指定的 L1 合约调用 StarkNet 核心合约的 consumeMessageFromL2()
注意:由于任何 L2 合约都可以向任何 L1 合约发送消息,因此建议 L1 合约在处理交易之前检查“From”地址。
后面我们会展示如何使用这种机制来实现L2->L1的withdraw。
另一个方向类似地:
L1 合约(在 L1 链上)调用StarkNet 核心合约的 send_message() 函数,该函数会存储这个消息。消息中包含一个附加字段——“selector”,指示了相应的 L2 合约中应调用什么函数。
StarkNet Sequencer会自动消费消息,调用由L2合约的function。 目标L2合约由“to”地址指定,目标函数由selector字段指定。
这个方向的消息可用于L1->L2的“Deposit”
注意,虽然诚实的Starknet Sequencer会自动消费所有 L1 -> L2 消息,但Startnet协议并未强制这一点. 因此 Sequencer有可能会选择跳过消息。在设计两个合约之间的消息协议时应该考虑到这一点。
重要提示:StarkNet Alpha 系统仍在开发中,因此系统状态会不时重置并删除所有合约。这意味着你不应该将有价值的资产转移到 StarkNet 系统,除非你确信能把资产withdraw回L1。That being said, recall that StarkNet Alpha only runs on the Goerli testnet.
在本节中,我们将构建一个简单的跨链桥——用户将能够存入 L2代币,他们的L2 余额将会增加。 然后,从L2转一些代币到L1,这将减少 L2 帐户余额并相应增加 L1帐户余额。
%lang starknet
from starkware.cairo.common.alloc import alloc
from starkware.cairo.common.cairo_builtins import HashBuiltin
from starkware.cairo.common.math import assert_nn
from starkware.starknet.common.**messages import send_message_to_l1
const L1_CONTRACT_ADDRESS = (0x2Db8c2615db39a5eD8750B87aC8F217485BE11EC)
const MESSAGE_WITHDRAW = 0
注意,在实际应用中,可能会使用@storage_var存储L1 Contract Address,而不是使用一个Constant.
@storage_var
func balance(user : felt) -> (res : felt):
end
@view
func get_balance {
syscall_ptr : felt*
pedersen_ptr : HashBuiltin*,
range_check_ptr,
}(user : felt) -> (balance : felt):
let (res) = balance.read(user=user)
return (res)
end
这样我们能有一些“资金”可以玩。下面定义了一个可以铸造新代币的函数。在实际应用中,你可能不想要一个让用户直接印钱的函数。此外,需要检查"amoun"t参数是否为负数。
// --------印钱 -------
@external
func increase_balance{
syscall_ptr : felt*,
pedersen_ptr : HashBuiltin*,
range_check_ptr,
}(user : felt, amount : felt):
let (res) = balance.read(user=user)
balance.write(user, res + amount)
return ()
end
L2->L1消息可用于从L2向L1转账(widthdraw):
请求提款(withdraw)的用户发起一个L2 withdraw 交易
这个交易(transaction)会减少L2 帐号余额,并向 L1 合约发送消息,要求将用户的 L1 余额增加相应金额。
跨链桥L1侧合约会消费这个消息,并按消息中的请求, 增加帐户 L1 余额。
@external
func withdraw {
syscall_ptr : felt*,
pedersen_ptr : HashBuiltin*,
range_check_ptr,
}(user : felt, amount : felt):
# Make sure 'amount' is positive.
assert_nn(amount)
let (res) = balance.read(user=user)
tempvar new_balance = res - amount
# Make sure the new balance will be positive.
assert_nn(new_balance)
# Update the new balance.
balance.write(user, new_balance)
# Send the withdrawal message.
let (message_payload : felt*) = alloc()
assert message_payload[0] = MESSAGE_WITHDRAW
assert message_payload[1] = user
assert message_payload[2] = amount
send_message_to_l1( to_address=L1_CONTRACT_ADDRESS,
payload_size=3,
payload=message_payload,
)
return ()
请注意,添加了一个新的隐式参数@syscall_ptr, 这个参数允许我们调用 StarkNet OS的一些函数,如send_message_to_l1()
通过调用 send_message_to_l1() 发送消息. 这个方法需要获取 L1 合约地址、消息大小和消息本身(类型为”felt“)。请注意,消息本身是作为指针给出的,因此必须显式传递消息长度。在我们的示例中,消息数据为:MESSAGE_WITHDRAW、用户、金额。我们选择使用第一个元素作为消息类型的指示符(请注意,我们在这里并不真正需要它,因为我们只有一个消息类型)。
至于如何编写跨链桥的 L1侧合约, 可参考: https://starknet.io/docs/_static/L1L2Example.sol 。可以研究一下其中的withdraw()函数:它获取用户和金额,消费消息(如果没有在链上收到消息,这部分将失败)并相应地更新用户的余额。正如下面代码所示,我们将 L2 合约的地址作为参数传递给函数,这样这个L1合约一旦部署后,本教程的学习者都可以直接调用它。但是实际应用中, 用 L2 合约的地址作为参数通常是没有意义的——实际应用中地址应该是固定的。
为了处理从 L1 合约发送的消息,应声明一个@l1_handler
@l1_handler
func deposit{
syscall_ptr : felt*,
pedersen_ptr : HashBuiltin*,
range_check_ptr,
}(from_address : felt, user : felt, amount : felt):
# Make sure the message was sent by the intended L1 contract.
assert from_address = L1_CONTRACT_ADDRESS
# Read the current balance.
let (res) = balance.read(user=user)
# Compute and update the new balance.
tempvar new_balance = res + amount
balance.write(user, new_balance)
return ()
end
Starknet OS会调用Handler处理L1的message。一个合约可以定义多个Handler。Handler由一个称为Selector的整数值标识。可以使用下面的Python代码计算一个Handler的Selector值。
from starkware.starknet.compiler.compile import \
get_selector_from_name
print**(get_selector_from_name('deposit'))
运行结果:
当 L1 合约想要发送消息时,它会调用 StarkNet Core 合约的 sendMessageToL2() 函数,并指定 L2 合约地址和要调用的处理程序的selector。 请参考https://starknet.io/docs/_static/L1L2Example.sol中的function deposit。
将上述代码保存至文件l 1l2.cairo, 或点击这里下载: here
$ starknet-compile l1l2.cairo \
--output l1l2_compiled.json \
--abi l1l2_abi.json
$ starknet deploy --contract l1l2_compiled.json
# The deployment address of the previous contract.
export CONTRACT_ADDRESS="<address of the previous contract>"
export USERID="<favorite 251-bit integer>"
$ starknet invoke \
--address ${CONTRACT_ADDRESS} \
--abi l1l2_abi.json \
--function increase_balance \
--inputs \
${USERID} 3333
调用合约的withdraw() 函数,使用以下参数:
CONTRACT_ADDRESS, USERID, 1000
和以前一样CONTRACT_ADDRESS 是 L2 合约的地址 (见上文export的环境变量)。
$ starknet invoke \
--address ${CONTRACT_ADDRESS} \
--abi l1l2_abi.json \
--function withdraw \
--inputs \
${USERID} 1000
要等待交易被区块链接受(这可能需要一些时间)。 期间您可以使用 starknet tx_status 来跟踪交易的进度。
检查结果: 应为 3333 - 1000 = 2333块.
在withdraw()交易之后,用户余额应为:
L1 余额 : 1000, L2 余额 : 2333
$. starknet call \
--address ${CONTRACT_ADDRESS} \
--abi l1l2_abi.json \
--function get_balance \
--inputs \
${USERID}
结果:
2333
调用合约的 deposit() 函数, 从L1转账600块到L2。
使用以下参数:
CONTRACT_ADDRESS、USERID、600
StarkNet 可能需要一些时间来处理传入消息并调用 L1 Handler(例如,系统等待一些区块链确认)。 但在那之后,您将能够通过再次调用 get_balance 的 starknet 来查看用户的更新余额。
新余额应为
L1 余额 : 400, L2 余额 : 2933。
No comments yet