在这个简短的概述中,咱们描述了咱们在eToro标记化资产背后实施技术的初步经验,即MoveIR语言中的(eToken),用于在Libra网络上进行部署。数据库
Libra协议是一个肯定性状态机,它将数据存储在版本化数据库中。使用新颖的领域特定语言:Move。Move容许可编程事务和模块重用代码和状态 - 相似于咱们定义为智能合约。编程
目前,Libra不容许发布模块,这就是为何eToken的源代码应该做为测试运行的缘由。能够在此处找到此Move IR eToken实现的完整源代码。安全
此测试已在提交哈希中使用Libra版本执行#004d472。能够在此处找到存储库。微信
Move中间表示语言
随着Libra的发布,定义了一种名为Move的新的特定于域的语言。直到如今,Libra团队还没有提供更高级别的实施。随着该协议的最近宣布,一个名为'Move IR'的lanaguge的一个不太符合人体工程学的中间表示被释放网络
Move内化了内存全部权和借用的概念,与Rust的运做方式很是类似。然而,Move语言的新颖性是定义“资源”的方式。dom
“资源”是一种利用全部权模型的结构数据类型,但永远不能仅复制Move和借用。这是Move语言的核心功能,由于它保证没有定义的资源意外重复,从而消除了双重支出或从新发生攻击的可能性。ide
所以,“资源”的概念与数字资产概念很好地对应是明智的函数
eToken实施工具
modules: module Capability { // Capability is responsible for declaring an account's permissions. // We define the notion of an owner, minter and blacklisted. // Only the owner can assign blacklisted and minter capabilities. // The owner is defined by hardcoding its account before publishing the module. // ----------------------------------------------------------------- // Declare owner as a resource. It's only meant to be published once resource Owner { } // Declare a resource that declares an address's capabilities. // Every address using EToken will need to publish a resource themselves. // However, only the owner can change its content. resource T { minter: bool, blacklisted: bool, } // Every account should execute this once before using Capability and EToken module. // If the sender is the hardcoded owner, then owner capability is published. // Reverts if already published public publish() { let sender: address; sender = get_txn_sender(); // Publish owner capability if sender is the privileged account // Uncomment the following line in production and use a real owner account: if (move(sender) == 0x0) { if (true) { // Always branch to here when testing, otherwise the test can't complete as the sender address is randomly chosen. Self.grant_owner_capability(); } // Publish a new capability with no permissions. move_to_sender<T>(T{ minter: false, blacklisted: false }); return; } // Internal function that grants owner capability grant_owner_capability() { move_to_sender<Owner>(Owner {}); return; } // Grants minter capability to receiver, but can only succeed if sender owns the owner capability. public grant_minter_capability(receiver: address, owner_capability: &R#Self.Owner) { let capability_ref: &mut R#Self.T; release(move(owner_capability)); // Pull a mutable reference to the receiver's capability, and change its permission. capability_ref = borrow_global<T>(move(receiver)); *(&mut move(capability_ref).minter) = true; return; } // Grants blacklist capability to receiver, but can only succeed if sender owns the owner capability. public grant_blacklisted_capability(receiver: address, owner_capability: &R#Self.Owner) { let capability_ref: &mut R#Self.T; release(move(owner_capability)); // Pull a mutable reference to the receiver's capability, and change its permission. capability_ref = borrow_global<T>(move(receiver)); *(&mut move(capability_ref).blacklisted) = true; return; } // This returns an immutable reference to the owner capability if it exists. // Is used by the owner to show ownership to privileged functions. // Reverts if owner capability does not exist. public borrow_owner_capability(): &R#Self.Owner { let sender: address; let owner_capability_ref: &mut R#Self.Owner; let owner_capability_immut_ref: &R#Self.Owner; sender = get_txn_sender(); owner_capability_ref = borrow_global<Owner>(move(sender)); owner_capability_immut_ref = freeze(move(owner_capability_ref)); return move(owner_capability_immut_ref); } // This returns an immutable reference to the general capability if it exists. // Should be used by every account to prove capabilities. // Reverts if capability does not exist. public borrow_capability(): &R#Self.T { let sender: address; let capability_ref: &mut R#Self.T; let capability_immut_ref: &R#Self.T; sender = get_txn_sender(); capability_ref = borrow_global<T>(move(sender)); capability_immut_ref = freeze(move(capability_ref)); return move(capability_immut_ref); } // Return whether the capability allows minting. public is_minter(capability: &R#Self.T): bool { let is_minter: bool; is_minter = *(&move(capability).minter); return move(is_minter); } // Return true the capability is not blacklisted. public is_not_blacklisted(capability: &R#Self.T): bool { let is_blacklisted: bool; is_blacklisted = *(&move(capability).blacklisted); return !move(is_blacklisted); } // Reverts if capability does not allow minting public require_minter(capability: &R#Self.T) { let is_minter: bool; is_minter = Self.is_minter(move(capability)); assert(move(is_minter), 0); return; } // Reverts if capability is blacklisted public require_not_blacklisted(capability: &R#Self.T) { let is_not_blacklisted: bool; is_not_blacklisted = Self.is_not_blacklisted(move(capability)); assert(move(is_not_blacklisted), 0); return; } } module EToken { // This module is responsible for an actual eToken. // For it to be useful a capability has to be published by using the Capability module above. // ----------------------------------------------------------------- import Transaction.Capability; // Declare the eToken resource, storing an account's total balance. resource T { value: u64, } // Publishes an initial zero eToken to the sender. // Should be called once before using this module. public publish() { move_to_sender<T>(T{ value: 0 }); return; } // Mint new eTokens. // Reverts if capability does not allow it. public mint(value: u64, capability: &R#Capability.T): R#Self.T { Capability.require_minter(move(capability)); return T{value: move(value)}; } // Returns an account's eToken balance. // Reverts if an initial eToken hasn't been published. public balance(): u64 { let sender: address; let token_ref: &mut R#Self.T; let token_value: u64; sender = get_txn_sender(); token_ref = borrow_global<T>(move(sender)); token_value = *(&move(token_ref).value); return move(token_value); } // Deposit owned tokens to an payee's address, and destroy the tokens to deposit, // Reverts if user is blacklisted. public deposit(payee: address, to_deposit: R#Self.T, capability: &R#Capability.T) { let payee_token_ref: &mut R#Self.T; let payee_token_value: u64; let to_deposit_value: u64; Capability.require_not_blacklisted(move(capability)); payee_token_ref = borrow_global<T>(move(payee)); payee_token_value = *(©(payee_token_ref).value); // Unpack and destroy to_deposit tokens T{ value: to_deposit_value } = move(to_deposit); // Increase the payees balance with the destroyed token amount *(&mut move(payee_token_ref).value) = move(payee_token_value) + move(to_deposit_value); return; } // Withdraw an amount of tokens of the sender and return it. // This works by splitting the token published and returning the specified amount as tokens. public withdraw(amount: u64, capability: &R#Capability.T): R#Self.T { let sender: address; let sender_token_ref: &mut R#Self.T; let value: u64; Capability.require_not_blacklisted(move(capability)); sender = get_txn_sender(); sender_token_ref = borrow_global<T>(move(sender)); value = *(©(sender_token_ref).value); // Make sure that sender has enough tokens assert(copy(value) >= copy(amount), 1); // Split the senders token and return the amount specified *(&mut move(sender_token_ref).value) = move(value) - copy(amount); return T{ value: move(amount) }; } } script: // Performs simple testing to crudely verify the published modules above. import Transaction.Capability; import Transaction.EToken; main() { let sender: address; let owner_capability: &R#Capability.Owner; let capability: &R#Capability.T; let minted_tokens: R#EToken.T; let balance: u64; sender = get_txn_sender(); // Publish initial capability Capability.publish(); // Borrow owner_capability for minter delegation owner_capability = Capability.borrow_owner_capability(); // Delegate itself as a minter Capability.grant_minter_capability(copy(sender), move(owner_capability)); // Borrow general capability for proof of minting capability capability = Capability.borrow_capability(); // Publish an eToken account EToken.publish(); // Mint 100 eTokens and prove minter capability minted_tokens = EToken.mint(100, copy(capability)); // Deposit the freshly minted tokens to itself EToken.deposit(move(sender), move(minted_tokens), move(capability)); // Test that the balance corresponds with the intended behaviour balance = EToken.balance(); assert(move(balance) == 100, 3); return; }
eToken目前部署在以太坊区块链上,包括为生产中使用标记化而实现的几个重要功能。区块链
最重要的功能以下所列。粗体功能也已在Move IR中实现。
角色(破坏者,黑名单)
造币
燃烧
暂停
可升级
咱们将一个角色定义为Move实现中的一个功能,命名更改是为了遵照Libra本身的硬币实现的标准。
能力
为了可以授予minter和blacklist权限,咱们必须指定模块的全部者。做为模块的全部者,用户能够将账户添加为小工具和黑名单。
咱们首先定义全部者资源,该资源仅用于发布一次。它在Capability模块中声明。
resource Owner { }
而后,咱们经过将已发布资源Move到指定全部者来授予全部权。在这里,咱们在尝试使用该语言时遇到了一些问题,以保证全部者功能只发布一次。
在初始模块发布期间,Move IR定义彷佛不支持仅定义为可执行一次的函数,也称为构造函数。
固然,这将是授予“全部者”能力的理想场所。
此外,Move IR不直接支持全局变量,这多是将函数定义为已执行的不安全方式。
为了规避这些限制,咱们使用硬编码的全部者地址建立了模块,从而建立了单例资源。所以,只有在全部者做为发件人发布功能资源时才会执行全部权授予:
public publish() {
let sender: address;
sender = get_txn_sender();
// Publish owner capability if sender is the privileged account
// Replace 0x0 address with a real owner account address
if (move(sender) == 0x0) {
Self.grant_owner_capability();
}
...
模块不能表明发件人以外的其余账户发布资源。从而,让账户彻底控制与他们相关的内容。publish()所以,该功能必须由但愿得到有效能力的全部账户执行,这对于进一步的令牌使用是强制性的。
Neverthelss,强制执行硬编码的全部者地址并非一个优雅的解决方案。咱们向Libra团队提出了这个问题,团队成员建议实施合成糖替换硬编码地址fx。Self.publisher_address。
实际全部权授予是经过调用内部函数来完成的,该函数grant_owner_capability()建立Owner资源并将其发布到发件人账户。这是经过执行如下表达式完成的:
move_to_sender<Owner>(Owner {});
经过将所述功能实现为内部,VM保证它只能由模块内部执行。经过仅在发件人是指定的全部者地址时调用该函数,咱们确保它只发布一次。
该publish()还发布没有权限调用它,由于须要进一步令牌使用的全部账户的能力。
它只会在已经存在的状况下恢复。
经过将全部权定义为资源,咱们能够确保它不能被复制。它还为咱们提供了一种使人愉快的类型安全的方式来保护特权功能,例如授予其余人的铸造能力。这是经过简单地要求借用Owner资源做为特权函数的参数来实现的。账户只能经过调用borrow_owner_capability()函数来获取借用的引用,若是函数存在于发送方地址,则返回借用的引用。如下摘录举例说明了全部者特权函数:
// Grants minter capability to receiver, but can only succeed if sender owns the owner capability.
public grant_minter_capability(receiver: address, owner_capability: &R#Self.Owner) {
let capability_ref: &mut R#Self.T;
release(move(owner_capability));
...
借用的全部者功能仅用于类型安全性,所以当即释放给发送方:若是功能成功执行,则会改变位于该receiver地址的功能资源。
...
// Pull a mutable reference to the receiver's capability, and change its permission.
capability_ref = borrow_global<T>(move(receiver));
*(&mut move(capability_ref).minter) = true;
...
代币
经过使用功能模块定义eToken的角色和权限,咱们如今能够继续实际的令牌实现。
咱们首先声明令牌资源,该资源包含所述令牌的数量。
resource T {
value: u64,
}
与其余智能合约语言相比,Move的优点显而易见。若是咱们要存放一些令牌,咱们必须控制令牌的内存全部权。咱们只能经过拆分现有的自有令牌(也称为撤销)或铸造新鲜令牌来得到此全部权。
这种全部权属性保证了相同的令牌不能存在于其余地方,从而消除了因错误复制而致使的错误,从而致使双重花费和其余错误行为。
此外,内存全部权模型还要求必须明确销毁拥有的令牌或将其Move到另外一个全部者。这能够保证令牌不会被意外锁定在模块内,永远不会被再次检索。
经过利用这种类型安全的属性,咱们能够定义存放自有令牌的功能。
public deposit(payee: address, to_deposit: R#Self.T, capability: &R#Capability.T) {
...
Capability.require_not_blacklisted(move(capability));
payee_token_ref = borrow_global<T>(move(payee));
payee_token_value = *(©(payee_token_ref).value);
// Unpack and destory to_deposit tokens
T{ value: to_deposit_value } = move(to_deposit);
// Increase the payees balance with the destroyed token amount
*(&mut move(payee_token_ref).value) = move(payee_token_value) + move(to_deposit_value);
...
咱们首先确保用户未被列入黑名单。接下来,咱们经过解压缩其内部数量变量来销毁所拥有的令牌。最后,咱们经过解压缩的金额增长收款人的代币。
与存款相反,当从发件人的账户中提取令牌时,咱们将令牌分红两部分,并将新令牌的全部权归还给来电者。
经过首先减小发件人令牌数量,而后返回新的令牌资源来完成拆分。
*(&mut move(sender_token_ref).value) = move(value) - copy(amount);
return T{ value: move(amount) };
结论
总而言之,Libra和Move IR是智能合约开发中值得欢迎的一步。拥有强大的资产保证能够帮助开发人员减小容易出错的代码并加快Move速度。
尽管如此,Move IR仍然处于早期阶段,而且在当前迭代中不是用户友好的。因为某种缘由,它被称为“中间表明”:-)
咱们将密切关注这一发展,并对新的发展感到兴奋。
尝试并运行测试
若是您有兴趣了解Libra网络和Move IR,咱们建议您运行测试以熟悉上述概念。要运行原始eToken实现测试,您应该执行如下步骤:
克隆Libra存储库(最好是在引言中声明的提交哈希)
跟随LibraREADME如何编译和安装它。
克隆此存储库
将位于src/eToken.mvir此存储库中的eToken Move IR源代码复制到位于的Libra存储库中的测试文件夹language/functional_tests/tests/testsuite/modules/
在Libra存储库中的某处执行如下命令: cargo test -p functional_tests eToken
Libra国内开发者微信交流群:
不能入群请加管理微信