From cbb23aef821f8b2cddee5fbd30dc98f5c9845094 Mon Sep 17 00:00:00 2001 From: davidhq Date: Tue, 26 Apr 2022 11:27:59 +0200 Subject: [PATCH] Add Yul smart contract language (#5861) * Add Yul smart contract langauge (intermediate language for Ethereum Virtual Machine) * add yul to grammars.yml * remove yulp extension Co-authored-by: Colin Seymour --- grammars.yml | 1 + lib/linguist/languages.yml | 8 ++ samples/Yul/contract.yul | 68 ++++++++++++++ samples/Yul/erc20.yul | 186 +++++++++++++++++++++++++++++++++++++ vendor/README.md | 1 + 5 files changed, 264 insertions(+) create mode 100644 samples/Yul/contract.yul create mode 100644 samples/Yul/erc20.yul diff --git a/grammars.yml b/grammars.yml index fb343fb97..47c29b5f9 100644 --- a/grammars.yml +++ b/grammars.yml @@ -127,6 +127,7 @@ vendor/grammars/SublimeClarion: vendor/grammars/SublimeEthereum: - source.solidity - source.vyper +- source.yul vendor/grammars/SublimeGDB/: - source.disasm - source.gdb diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index 912d47a6a..a47c5ff0f 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -7225,6 +7225,14 @@ Yacc: ace_mode: text color: "#4B6C4B" language_id: 409 +Yul: + type: programming + color: "#794932" + ace_mode: text + tm_scope: source.yul + extensions: + - ".yul" + language_id: 237469033 ZAP: type: programming color: "#0d665e" diff --git a/samples/Yul/contract.yul b/samples/Yul/contract.yul new file mode 100644 index 000000000..1d657937a --- /dev/null +++ b/samples/Yul/contract.yul @@ -0,0 +1,68 @@ +// A contract consists of a single object with sub-objects representing +// the code to be deployed or other contracts it can create. +// The single "code" node is the executable code of the object. +// Every (other) named object or data section is serialized and +// made accessible to the special built-in functions datacopy / dataoffset / datasize +// The current object, sub-objects and data items inside the current object +// are in scope. +object "Contract1" { + // This is the constructor code of the contract. + code { + function allocate(size) -> ptr { + ptr := mload(0x40) + if iszero(ptr) { ptr := 0x60 } + mstore(0x40, add(ptr, size)) + } + + // first create "Contract2" + let size := datasize("Contract2") + let offset := allocate(size) + // This will turn into codecopy for EVM + datacopy(offset, dataoffset("Contract2"), size) + // constructor parameter is a single number 0x1234 + mstore(add(offset, size), 0x1234) + pop(create(offset, add(size, 32), 0)) + + // now return the runtime object (the currently + // executing code is the constructor code) + size := datasize("Contract1_deployed") + offset := allocate(size) + // This will turn into a memory->memory copy for Ewasm and + // a codecopy for EVM + datacopy(offset, dataoffset("Contract1_deployed"), size) + return(offset, size) + } + + data "Table2" hex"4123" + + object "Contract1_deployed" { + code { + function allocate(size) -> ptr { + ptr := mload(0x40) + if iszero(ptr) { ptr := 0x60 } + mstore(0x40, add(ptr, size)) + } + + // runtime code + + mstore(0, "Hello, World!") + return(0, 0x20) + } + } + + // Embedded object. Use case is that the outside is a factory contract, + // and Contract2 is the code to be created by the factory + object "Contract2" { + code { + // code here ... + } + + object "Contract2_deployed" { + code { + // code here ... + } + } + + data "Table1" hex"4123" + } +} diff --git a/samples/Yul/erc20.yul b/samples/Yul/erc20.yul new file mode 100644 index 000000000..cbc04b571 --- /dev/null +++ b/samples/Yul/erc20.yul @@ -0,0 +1,186 @@ +object "Token" { + code { + // Store the creator in slot zero. + sstore(0, caller()) + + // Deploy the contract + datacopy(0, dataoffset("runtime"), datasize("runtime")) + return(0, datasize("runtime")) + } + object "runtime" { + code { + // Protection against sending Ether + require(iszero(callvalue())) + + // Dispatcher + switch selector() + case 0x70a08231 /* "balanceOf(address)" */ { + returnUint(balanceOf(decodeAsAddress(0))) + } + case 0x18160ddd /* "totalSupply()" */ { + returnUint(totalSupply()) + } + case 0xa9059cbb /* "transfer(address,uint256)" */ { + transfer(decodeAsAddress(0), decodeAsUint(1)) + returnTrue() + } + case 0x23b872dd /* "transferFrom(address,address,uint256)" */ { + transferFrom(decodeAsAddress(0), decodeAsAddress(1), decodeAsUint(2)) + returnTrue() + } + case 0x095ea7b3 /* "approve(address,uint256)" */ { + approve(decodeAsAddress(0), decodeAsUint(1)) + returnTrue() + } + case 0xdd62ed3e /* "allowance(address,address)" */ { + returnUint(allowance(decodeAsAddress(0), decodeAsAddress(1))) + } + case 0x40c10f19 /* "mint(address,uint256)" */ { + mint(decodeAsAddress(0), decodeAsUint(1)) + returnTrue() + } + default { + revert(0, 0) + } + + function mint(account, amount) { + require(calledByOwner()) + + mintTokens(amount) + addToBalance(account, amount) + emitTransfer(0, account, amount) + } + function transfer(to, amount) { + executeTransfer(caller(), to, amount) + } + function approve(spender, amount) { + revertIfZeroAddress(spender) + setAllowance(caller(), spender, amount) + emitApproval(caller(), spender, amount) + } + function transferFrom(from, to, amount) { + decreaseAllowanceBy(from, caller(), amount) + executeTransfer(from, to, amount) + } + + function executeTransfer(from, to, amount) { + revertIfZeroAddress(to) + deductFromBalance(from, amount) + addToBalance(to, amount) + emitTransfer(from, to, amount) + } + + + /* ---------- calldata decoding functions ----------- */ + function selector() -> s { + s := div(calldataload(0), 0x100000000000000000000000000000000000000000000000000000000) + } + + function decodeAsAddress(offset) -> v { + v := decodeAsUint(offset) + if iszero(iszero(and(v, not(0xffffffffffffffffffffffffffffffffffffffff)))) { + revert(0, 0) + } + } + function decodeAsUint(offset) -> v { + let pos := add(4, mul(offset, 0x20)) + if lt(calldatasize(), add(pos, 0x20)) { + revert(0, 0) + } + v := calldataload(pos) + } + /* ---------- calldata encoding functions ---------- */ + function returnUint(v) { + mstore(0, v) + return(0, 0x20) + } + function returnTrue() { + returnUint(1) + } + + /* -------- events ---------- */ + function emitTransfer(from, to, amount) { + let signatureHash := 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef + emitEvent(signatureHash, from, to, amount) + } + function emitApproval(from, spender, amount) { + let signatureHash := 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925 + emitEvent(signatureHash, from, spender, amount) + } + function emitEvent(signatureHash, indexed1, indexed2, nonIndexed) { + mstore(0, nonIndexed) + log3(0, 0x20, signatureHash, indexed1, indexed2) + } + + /* -------- storage layout ---------- */ + function ownerPos() -> p { p := 0 } + function totalSupplyPos() -> p { p := 1 } + function accountToStorageOffset(account) -> offset { + offset := add(0x1000, account) + } + function allowanceStorageOffset(account, spender) -> offset { + offset := accountToStorageOffset(account) + mstore(0, offset) + mstore(0x20, spender) + offset := keccak256(0, 0x40) + } + + /* -------- storage access ---------- */ + function owner() -> o { + o := sload(ownerPos()) + } + function totalSupply() -> supply { + supply := sload(totalSupplyPos()) + } + function mintTokens(amount) { + sstore(totalSupplyPos(), safeAdd(totalSupply(), amount)) + } + function balanceOf(account) -> bal { + bal := sload(accountToStorageOffset(account)) + } + function addToBalance(account, amount) { + let offset := accountToStorageOffset(account) + sstore(offset, safeAdd(sload(offset), amount)) + } + function deductFromBalance(account, amount) { + let offset := accountToStorageOffset(account) + let bal := sload(offset) + require(lte(amount, bal)) + sstore(offset, sub(bal, amount)) + } + function allowance(account, spender) -> amount { + amount := sload(allowanceStorageOffset(account, spender)) + } + function setAllowance(account, spender, amount) { + sstore(allowanceStorageOffset(account, spender), amount) + } + function decreaseAllowanceBy(account, spender, amount) { + let offset := allowanceStorageOffset(account, spender) + let currentAllowance := sload(offset) + require(lte(amount, currentAllowance)) + sstore(offset, sub(currentAllowance, amount)) + } + + /* ---------- utility functions ---------- */ + function lte(a, b) -> r { + r := iszero(gt(a, b)) + } + function gte(a, b) -> r { + r := iszero(lt(a, b)) + } + function safeAdd(a, b) -> r { + r := add(a, b) + if or(lt(r, a), lt(r, b)) { revert(0, 0) } + } + function calledByOwner() -> cbo { + cbo := eq(owner(), caller()) + } + function revertIfZeroAddress(addr) { + require(addr) + } + function require(condition) { + if iszero(condition) { revert(0, 0) } + } + } + } +} diff --git a/vendor/README.md b/vendor/README.md index 2869984fa..30d9c5b54 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -559,6 +559,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **YARA:** [infosec-intern/vscode-yara](https://github.com/infosec-intern/vscode-yara) - **YASnippet:** [Alhadis/language-emacs-lisp](https://github.com/Alhadis/language-emacs-lisp) - **Yacc:** [Alhadis/language-grammars](https://github.com/Alhadis/language-grammars) +- **Yul:** [davidhq/SublimeEthereum](https://github.com/davidhq/SublimeEthereum) - **ZAP:** [tclem/vscode-zil-language](https://github.com/tclem/vscode-zil-language) - **ZIL:** [tclem/vscode-zil-language](https://github.com/tclem/vscode-zil-language) - **Zeek:** [zeek/zeek-sublime](https://github.com/zeek/zeek-sublime)