こんにちはweb3開発者として活動してますLIBです。今回は新興のブロックチェーン領域のプログラミング言語Feの紹介です。
開発者にとって一般的に技術選定はとても重要なトピックです。登山に例えると、どんな靴で履いていくのか?それくらい重要だと筆者は捉えています。サンダルや安物のスニーカーで行ったら足が痛くなって、歩きにくくなって前に進みにくくなりますよね。
ブロックチェーン産業が進化を続け、新しいブロックチェーン・ネットワークが出現するにつれて、Solidity、Vyper、Moveなどの新しいプログラミング言語や、Rustのようなこの分野に導入される十分な古典的プログラミング言語も登場しています。
特にSolidityは本記事を読んでいる人知らない人がいないほど開発者に最も使われている言語でしょう。ではそんなSolidityの課題とはなんなのでしょうか?Feとは何でどんな使い方ができるのか?について記載していきます。
注意事項ですが、本記事ではどの技術を選ぶのかということではなく、幅広い選択肢を持っていくことが重要であるということを述べておきます。また細かい文法については解説しません。ご興味持ってすぐ実践してみたいという方は公式docs(https://fe-lang.org/docs/quickstart/index.html )からキャッチアップいただくことをお勧めいたします。
Solidityの課題
Solidityは高レベルのオブジェクト指向静的プログラミング言語で、もともとはスマートコントラクトを構築するためにEthereum Virtual Machine(EVM)上で動作しています。もともとはGavin Wood氏によって理論化され、最終的に2014年にChristian Reitwiessner氏によって開発されたSolidityは、関数、文字列操作、クラス、変数、算術演算など、JavaScriptなどの現代のほとんどのプログラミング言語で利用可能な概念を提供しています。
しかし同時に、Solidityを学ぶ前に注意しなければならないのは、Solidityの開発プロセスでは、時間内に発見するのが難しい隠れた欠陥が発生しやすいため、コードを徹底的に繰り返しテストする必要があるということです。
少し前のことですが、2021年には、暗号通貨業界全体で少なくとも189件のセキュリティ・インシデントが公に報告され、これらのセキュリティ・インシデントで少なくとも76億ドルの暗号資産が失われ、181件のDApp型セキュリティ・インシデントのうち、80%がスマート・コントラクトの脆弱性によるものだった。スマートコントラクトの脆弱性の原因のひとつは、Solidity言語自体の設計上の欠陥と、開発プロセス中に開発者によってもたらされたエラーであった。
今年2023年もweb3開発者の多くが使うライブラリであるopenzepplinのライブラリでさえもバグが見つかるほど、コードの安全性については開発者としては重要なテーマです。
Fe言語とは
◎概要
FeはEthereumブロックチェーンのための新しいスマートコントラクト言語です。Ethereum仮想マシン(EVM)用の静的型付け言語です。Rustにインスパイアされており、特にEthereumのエコシステムに参入したばかりのweb2開発者にとっても学びやすい言語と言われています。
少し余談ですが、11月に開催されたETHGlobal IstanbulではEthereum財団のPrizeの対象(https://ethglobal.com/events/istanbul/prizes#ethereum-foundation) がFeを活用するだけを対象としており、一部開発者の中からも注目され始めている言語です。
Xの投稿は2020年9月(https://twitter.com/official_fe/status/1308067497535471617) から開始しているため、3年以上の活動があるプロジェクトです。
Fe言語はEVMバイトコードにコンパイルされ、イーサリアムやEVM相当のブロックチェーンに直接デプロイできます。Fe言語のシンタックスは、RustやPythonの開発者にとっては馴染みのあるものという印象です。
Feのコントラクト簡単なコントラクトは以下の通りです。
contract GuestBook {
messages: Map<address, String<100>>
pub fn sign(mut self, ctx: Context, book_msg: String<100>) {
self.messages[ctx.msg_sender()] = book_msg
}
}
◎Feはどのような問題を解決するのか?
スマートコントラクトの言語が抱える問題の一つは、コンパイラが人間が読めるコードをEVMバイトコードに変換する際に曖昧さが生じることです。これは、セキュリティ上の欠陥や予期せぬ動作につながる可能性があることです。
また、EVMの詳細は、他の言語と比較して、高レベルの言語が直感的でなく、習得が難しい原因にもなる。Feが解決しようとしているのは、このような問題です。人間の読みやすさとバイトコードの予測可能性の両方を最大化するよう努力することで、FeはEVMで作業するすべての人に、より優れた開発者体験を提供します。
コードを比較してみましょう
Flash Loanをミニマムで作られているコントラクトで比較をしていきましょう。Flash Loanとは、非常に短期間で借り入れられ、同じトランザクションの中で返済される特殊な種類のローンです。トレーディング、アービトラージ、その他の金融活動で一時的な資金調達が必要な場合に使用されることが多いです。
FlashLoan コントラクト(Solidity):
-
receive()
: コントラクトがイーサリアムを受け取れるようにする。 -
owner
: コントラクトの所有者を保持します。 -
feePerMillion
: フラッシュローンの手数料率を保持します。 -
FlashLoanExecuted
イベント: フラッシュローンが実行された時に発火します。
言語それぞれの違い
Solidity | Fe | |
---|---|---|
言語のシンタックスと構造 | JavaScriptに似た文法を持ち、オブジェクト指向プログラミングの概念が強く反映されています。 | Pythonの文法に似ており、より直感的で簡潔なコードが書けるよう設計されています。 |
コントラクトの初期化 | コンストラクタconstructor()を使用して、オーナーを初期設定します。 | __init__メソッドを使用し、Pythonのコンストラクタに似た方法でオーナーを初期設定します。 |
メソッドと変数宣言 | 変数と関数のアクセス修飾子(public, externalなど)が明示的です。 | アクセス修飾子の代わりに、pubキーワードを使用して公開されるメソッドや変数を定義します。 |
エラーハンドリング | requireステートメントを用いて条件チェックとエラーハンドリングを行います。 | assertステートメントを使用し、条件が満たされない場合にエラーを投げます。 |
送金の操作 | transferメソッドとpayableキーワードを使用して、ETHの転送を行います。 | ctx.send_valueメソッドを使ってETHの転送を行います |
イベントの処理 | eventキーワードを使用してイベントを宣言し、emitキーワードでイベントを発行します。 | ctx.emitメソッドを用いてイベントを発行しますが、イベントの宣言方法が異なります。 |
コントラクトのインタフェース | interfaceを定義し、他のコントラクトとのインタラクションを規定します。 | 同様にインタフェースを定義することができますが、文法が異なります。 |
実際のコード
Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
interface IFlashLoanReceiver {
function executeOperation(
uint256 amount,
uint256 fee,
address initiator
) external;
}
contract FlashLoan {
receive() external payable {}
address public owner;
uint256 public feePerMillion; // Fee per million (1e6 = 100%)
event FlashLoanExecuted(address indexed borrower, uint256 amount);
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "FlashLoan: Only owner");
_;
}
function flashLoan(uint256 amount) external {
uint256 balanceBefore = address(this).balance;
require(balanceBefore >= amount, "FlashLoan: Insufficient liquidity");
uint256 fee = (amount * feePerMillion) / 1e6;
payable(msg.sender).transfer(amount);
IFlashLoanReceiver(msg.sender).executeOperation(
amount,
fee,
msg.sender
);
require(
address(this).balance >= balanceBefore + fee,
"FlashLoan: Loan not repaid with fee"
);
emit FlashLoanExecuted(msg.sender, amount);
}
function setOwner(address _owner) onlyOwner external {
owner = _owner;
}
function setFeePerMillion(uint256 _feePerMillion) onlyOwner external {
require(_feePerMillion <= 1e6, "FlashLoan: Fee too high");
feePerMillion = _feePerMillion;
}
function withdraw(address recipient, uint256 amount) onlyOwner external {
payable(recipient).transfer(amount);
}
}
Fe
use std::evm
struct FlashLoanExecuted {
#indexed
pub borrower: address
pub amount: u256
}
contract IFlashLoanReceiver {
pub fn executeOperation(self, amount: u256, fee: u256, initiator: address) {
revert
}
}
contract FlashLoan {
owner: address
feePerMillion: u256 // Fee per million (1e6 = 100%)
fn only_owner(self, ctx: Context) {
assert ctx.msg_sender() == self.owner, "FlashLoan: Only owner"
}
pub fn __init__(mut self, ctx: Context) {
self.owner = ctx.msg_sender()
}
pub fn flashLoan(mut self, mut ctx: Context, amount: u256) {
let mut balance_before: u256
unsafe {
balance_before = evm::balance()
}
assert balance_before >= amount, "FlashLoan: Insufficient liquidity"
let fee: u256 = (amount * self.feePerMillion) / 1000000
ctx.send_value(to: ctx.msg_sender(), wei: amount)
let mut receiver: IFlashLoanReceiver = IFlashLoanReceiver(ctx.msg_sender())
receiver.executeOperation(
amount: amount,
fee: fee,
initiator: ctx.msg_sender()
)
let mut balance_after: u256
unsafe {
balance_after = evm::balance()
}
assert balance_after >= balance_before + fee, "FlashLoan: Loan not repaid with fee"
ctx.emit(FlashLoanExecuted(borrower: ctx.msg_sender(), amount: amount))
}
pub fn owner(self) -> address {
return self.owner
}
pub fn setOwner(mut self, ctx: Context, _owner: address) {
self.only_owner(ctx)
self.owner = _owner
}
pub fn feePerMillion(self) -> u256 {
return self.feePerMillion
}
pub fn setFeePerMillion(mut self, ctx: Context, _feePerMillion: u256) {
self.only_owner(ctx)
assert _feePerMillion < 1000000, "FlashLoan: Fee too high"
self.feePerMillion = _feePerMillion
}
pub fn withdraw(self, mut ctx: Context, recipient: address, amount: u256) {
self.only_owner(ctx)
ctx.send_value(to: recipient, wei: amount)
}
}
contract Vm {
pub fn prank(self, addr: address) {
revert
}
pub fn startPrank(self, addr: address) {
revert
}
pub fn stopPrank(self) {
revert
}
pub fn expectRevert(self) {
revert
}
}
contract FlashLoanTest {
vm: Vm
pub fn __init__(mut self) {
let vmAddr: address = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D
self.vm = Vm(vmAddr)
}
pub fn test(self, mut ctx: Context, loanAddr: address, receiver: address) {
let feePerMillion: u256 = 1000
let liquidity: u256 = 1000000
let amount: u256 = 1000
let mut flashLoan: FlashLoan = FlashLoan(loanAddr)
flashLoan.setFeePerMillion(ctx: ctx, _feePerMillion: feePerMillion)
ctx.send_value(to: loanAddr, wei: liquidity)
ctx.send_value(to: receiver, wei: liquidity)
self.vm.prank(addr: receiver)
flashLoan.flashLoan(ctx: ctx, amount: amount)
let mut receiverBalance: u256
unsafe {
receiverBalance = evm::balance_of(receiver)
}
assert receiverBalance == liquidity - amount * feePerMillion / 1000000, "Incorrect balance"
}
}
最後に
いかがでしたでしょうか? 総合的に見ると、Fe言語はRustやPythonの要素を取り入れることで、学習しやすく、安全性を重視し、効率的なコーディングが可能な設計になっている様に見えます。
ただし、Feがまだ新しい言語であり、実際のユースケースもこれからという段階ではあるため実践的な使用例が増えていくにつれて、その長所と短所がより明確になると考えられます。
実際に手を動かしてみたい方は以下の環境構築から手を動かしてみてください。
Appendix
Fe言語公式情報
公式サイト: https://fe-lang.org/
X: https://twitter.com/official_fe
Discord: https://discord.com/invite/ywpkAXFjZH
Github: https://github.com/ethereum/fe
Flashloadのソースコード
https://github.com/therealbytes/fefe