fuellabs / sway Goto Github PK
View Code? Open in Web Editor NEW๐ด Empowering everyone to build reliable and efficient smart contracts.
Home Page: https://fuellabs.github.io/sway
License: Apache License 2.0
๐ด Empowering everyone to build reliable and efficient smart contracts.
Home Page: https://fuellabs.github.io/sway
License: Apache License 2.0
Currently multiline comments are not supported/handled
As well as inline comments, i.e.
let num = 42; // this is the answer
would end up formatted in 2 different lines
let num = 42;
// this is the answer
If two files declare each other as dep
s, we currently overflow the stack. This is not ideal
In the Solidity world, functions are hashed using a specific algorithm and then truncated for 4 bytes. We need to decide if we want to follow the same scheme (it sounds like we do), and implement it. These will then be baked into the contract preamble section in the ABI/"switch statement" for contracts.
Assigning Victor for now since he seemed to have interest, but feel free to reassign if that is not the case.
Credit to @leviathanbeak for finding this one. In function applications only the first argument is getting added to the parse tree. This looks like a recent regression, so the fix should include testing to prevent it from happening again.
Minimal repro:
script;
fn main() {
foo(10, 20);
}
fn foo(a: u64, b: u64) -> u64 {
10
}
and this is the flawed function application:
FunctionApplication {
name: CallPath {
prefixes: [],
suffix: Ident {
primary_name: "foo",
span: Span {
str: "foo",
start: 24,
end: 27,
},
},
},
arguments: [
Literal {
value: U64(
10,
),
span: Span {
str: "10",
start: 28,
end: 30,
},
},
],
span: Span {
str: "foo(10, 20)",
start: 24,
end: 35,
},
},
mdBook uses highlight.js for syntax highlighting. This can be extended for custom languages. Add support for Sway syntax highlighting for the docs book.
For single-line comma-separated lists (e.g. the fields of a struct) forc fmt
currently:
It should support certain comma-separated lists always being one element per line (e.g. struct fields will always be one per line, no one writes them in a single line), which should also fix the abovementioned bug.
Solidity has four visibility modifiers distinguish both a function's internal visibility and its external (ABI) visibility. The cross product of these two external/internal decisions means four options:
Externally callable | Not externally callable | ||
---|---|---|---|
Internally callable | public |
internal |
|
Not internally callable | external |
private |
There are two issues with this:
I propose that we use something similar to Rust's pub
visibility modifier. One keyword with parameters to control the more precise meaning.
The following is an example of what the four Solidity visibility options could look like with this Rust approach.
Externally callable | Not externally callable | ||
---|---|---|---|
Internally callable | pub fn |
pub(contract) fn (or pub(script) fn ) |
|
Not internally callable | pub(abi) fn |
fn |
The actual specifics of the wording (abi
, contract
, maybe even reusing rust's crate
or just saying lib
) are not really important to me, so feel free to propose alternatives. The idea here is to parameterize using this pub
keyword. This also makes it easier to expand upon visibility options later without introducing extra reserved words or noise to the grammar.
Currently, an entire file is wrapped with one of: library
, script
, predicate
, or contract
. As you can only have one such declaration per file, this nesting is unnecessary. We should instead just have a keyword at the beginning of the program and no nesting.
We'd like to support the following primitive types:
u8
, u16
, u32
, u64
. Note that the FuelVM only has unsigned integer support for now, as signed integers are of dubious value in the context of smart contracts. Also note that the FuelVM is big-endian.bool
: Standard Boolean.byte
: A single byte. While we could use u8
like Rust, having a distinct type that forbids certain operations (e.g. arithmetic) without explicit casting increases type safety.bytes32
: A 32-byte hash digest.address
: A (for now) 32-byte address. This should be a parameter, since we may opt for shorter addresses in the future.Additionally:
We expect blockchain applications to make heavy use of byte manipulation, and so we should promote these types to primitive types rather than having users go through a standard library for them.
We can use this issue to track requirements for the package manager/workflow tool.
Writing code:
Testing
Deployment
The following code pattern should throw an error, but it doesn't.
let x = if maybe_true {
6
};
There should be an else
.
This issue tracks various features we want out of the command-line and IDE integration tooling surrounding the HLL. cc @sezna @SilentCicero
The CLI is tentatively called the "Fuel Orchestrator" forc
. It is intended to be the equivalent of Rust's cargo
.
cargo new
. (#48)rustfmt
. (#80)In addition to a CLI tool associated with a single compiler version, and equivalent to Rust's rustup
tool is needed to manage multiple compiler versions. Blockchains require deterministic contract compilation for verifiability, which requires being able to manage multiple compiler versions. Ref: FuelLabs/fuelup#1.
Integration as an IDE plugin. Specifically, as an extension to Visual Studio Code similar to this one for Rust. Additional IDEs are reserved for the distant future.
The framework is there but it just needs to be added to the grammar.
E.G.
use thing::MyStruct as ThisStruct;
Often, you want to display help text or render multiple spans in an error.
To support all the functionality of the transaction format supported with Fuel v2 I suggest the following top-level declarations:
contract
: defines a list of externally-callable functions that will be part of the contract's ABI, and a list of internally callable functions that are not part of the ABI. The ABI essentially replaces a main function with a giant switch statement to call different functions. Contracts will persist in the FuelVM's state.contract {}
script
: defines a single function that operates at the transaction level. Scripts exist only for the duration of a transaction.script (input) -> output {}
predicate
: defines a single function that must return a bool
that operates at the input level. Predicates only exist for the duration of verifying the unlocking condition of an input.predicate (input) -> bool {}
Smart contract developers will only use the first for writing their contract. Wallet and surrounding tooling can develop scripts and predicates using the latter two. But integration within a single language is important so that calls between them (say, a contract's function being called by a script) are type-safe, etc.
I am not implementing this in the MVP, but to prevent random pollution of the method namespace of types when you import libraries, Rust prevents you from implementing a trait for a type when you don't own either the trait or the type. We should do this eventually.
Memory structures:
Basics:
Return the result from the main
function, or the public ABI functions in the case of contracts, using the return
opcode.
If the value is not held in one register, i.e. if it is a reference to some stack or heap memory, do we need to copy that memory somewhere? cc @adlerjohn
Currently (as of #81) the forc fmt
command simply displays a list of unformatted files. This isn't the worst, but we would like it to instead display a diff for each file. This shouldn't be too hard since the logic already has the original and formatted text, and I'm sure there are diff crates in Rust.
Now that multiple files can be compiled at once, we need to include the filename in errors and warnings. This will touch a lot of lines of code, so I think it should be a separate PR from the implementation of include statements themselves.
Forc now allows users to specify remote dependencies hosted on GitHub (#101), however, once a dependency at a given reference (either default, branch, or tag) has been downloaded, there's no way to check if there are newer updates to it (i.e fresher commits on top of a reference) and actually perform the updates.
Add another command to Forc's CLI, update
, that will check for updates on the dependencies in Forc.toml
that have git
set and perform these updates; downloading the new one and replacing the old one.
forc update
should:
Non-functional requirements needed:
forc_build
and (the new) forc_update
. This needs to be refactored.[1]: We store the hash in the name of the dependency's locally installed directory
Currently the compiler is one-pass, which means it is up to the user to declare things before they are used. As this is a modern language, we should do an initial pass for usages, and a secondary pass for matching up definitions. This can be implemented in our codebase by delaying namespace lookups until type checking, and removing any namespace checks in the parsing stage.
I wanted to leave the title of this issue rather generic so we can discuss all alternative options to solve this.
My proposal is to leverage the type system in a way similar to Haskell's monads: expressing side effects (both global access as well as true "effects") in terms of wrapper types. Whether or not they are actual mathematical monads is not as important to me, but I might use the term occasionally due to my familiarity with it and lack of a better term (for now).
In Haskell, if a function accesses stdout
, its function signature must reflect this. As every function in Haskell contains at most one expression, this is easy: operators over monads must operate on the inner types, and you must handle the type compatibility throughout the entire function.
An example:
main :: IO ()
main = putStrLn "hi"
This means that the function accesses I/0 (stdout) and returns nothing (()
). If you get a number from IO, it might look like this:
readNum :: IO Integer
readNum = prompt "Please enter a number"
This can, in some sense, be read as "the Integer returned by readNum
has been polluted by outside state". From here on out, any interactions with that type must also be wrapped in IO. Basically, anything in the tree that touches this type is also IO, since it is in some way impacted by IO. The same "type wrapping" or "type pollution" happens if you access any side effect: DB access, network requests, API calls, etc. If you add an Integer
to an IO Integer
, you must get an IO Integer
back out of that.
Rust does something similar with Fn
traits: FnMut
, FnOnce
, etc. I'll leave researching those up to y'all.
My suggestion here is not to do exactly what Haskell does, as we are making a procedural/imperative language that is not fully functional, and it would be incompatible. However, we can do something similar.
// deterministic, pure, beautiful
fn my_func() -> u32 {
return 5u32;
}
// polluted and corrupt with the machinations of society (global state)
fn side_effect(&self) -> State<u32> {
return self.state.some_num; // not sure how state access will work yet, this is an approximation
}
// working with a `State` type. Note that the function return type is `State` simply because it accesses state, even though it doesn't return it. The compiler will have to enforce this.
fn adds_to_state(&self) -> State<()> {
let _ = self.side_effect();
return;
}
fn mutates_state(&mut self) -> MutState<u32> {
self.state.some_num = self.state.some_num + 1;
return self.state.some_num;
}
// They would be generic types so we could have operators and things work on them without any loss of ergonomics.
impl std::ops::Add for MutState<T> where T: std::ops::Add {
fn add(&self, other: &T) -> MutState<T> {
MutState(self.0 + other)
}
}
I don't like this idea as much, but for the sake of having options, we can also mimic Solidity and add more keywords to function definitions and yell at the programmer if they don't line up.
pure fn my_func(&self) -> u32 {
return 5u32;
}
view fn side_effect(&self) -> u32 {
return self.state.some_num;
}
// this to me is not as clear, and your types don't reflect where in the function you are accessing state.
// for example, we could call 10 functions, and you don't know which one is requiring this function to be a `view` fn without checking every single one.
view fn uses_side_effect(&self) -> u32 {
foo();
non_view_fn();
etc();
return self.side_effect();
}
As we currently naively inline every function call, I am pretty sure we will go into infinite recursion in the compiler if a function calls itself. I should now implement normal function declaration bytecode generation where you jump to a function definition and have a call stack, and make it toggleable.
I'm skipping this in the initial big PR but will implement it shortly after.
When executing a CALL
, the callee receives a pointer to a byte array, which isn't type-safe. When executing RETURN
, the caller receives a pointer to a byte array, which also isn't type-safe.
The current way to resolve this is to generate bytecode that does type-checking at runtime, in order to guarantee the byte arrays can be considered of the right type. As an example, if the underlying value is a byte[32]
(i.e. a hash digest), then the untyped byte array should have a length of exactly 32. No further checks are necessary for this type.
One issue that arises here is the possibility of aliasing, especially with collections like Vec
. If the backing arrays of two Vec
variables are actually the same, then all guarantees of the borrow checker are gone. Of course, every returned Vec
could be considered as aliased, but that seems like it would be not-great for usability. It might be costly to ensure there is no aliasing at runtime, but maybe not?
Another way of resolving this would be to add an allocator to the VM itself, but that's something we ideally want to avoid.
Was thinking through that many of our developers will be coming from NPM not Rust or cargo. If possible, in the future, we should provide a simple way to run a binary of our CLI / language via npm.
npm install -g fume
This way we make our language incredibly accessible via a simple NPM install.
Of course, we would want it available via all the usual candidates (from source, homebrew, cargo etc.).
Add a new command, forc deploy
, which will deploy a contract.
Deploy will fail if the current project is not a contract. If the current project is a contract, compile it, then craft and print a transaction that will create the contract. The transaction is of type TransactionCreate
.
bytecodeLength
should be set to the length of the contract bytecode, in instructions.bytecodeWitnessIndex
should be set to 0
.witnessesCount
should be set to 1
.witnesses
should contain a single element: a Witness
with the contract bytecode.outputsCount
should be set to 1
.outputs
should contain a single element of type OutputContractCreated
, with the zero hash. In the future (once Merkle trees are working), it will have to be set to the contract ID properly.(Later on, this command will also send the transaction to a running client. But that can be saved for a future PR.)
Currently there are no tests for the formatter, we should have extensive test cases for it
We need to bikeshed on a name for the high-level language, ideally one with good SEO.
My suggestion is "Fumes" (as in fumes from gas).
On master, CI should build for amd64
and arm64
, and upload the binaries named as nightly-timestamp-commithash
to a private repo with deploy key.
Imports right now are going to be implemented as basic filepaths. When we get a real package manager, we will want to remove this behavior. Additionally, imports are probably going to require a specific design, due to the nature of importing for a contract call ABI vs just a library import. maybe use from chain
vs use
?
Right now, you can use
from the root of a project like this:
use ::foo::Foo;
You cannot directly refer to something like this:
let my_enum = ::foo::MyEnum;
It isn't hard, it just needs to be done. The place to implement this would be in the places where false
is passed directly in to find_module
.
We would like to have a way for projects to install Sway project dependencies, so that Sway contract devs can share and re-use code. While eventually a central repository of published authenticated packages will be nice, in the meantime installing packages from simple GitHub repo URLs will suffice.
A field in the manifest file already exists for dependencies, however only handles local dependencies.
sway/forc/src/utils/manifest.rs
Lines 28 to 46 in 8b36344
These dependencies are compiled when running forc build
.
sway/forc/src/ops/forc_build.rs
Lines 68 to 77 in 9569c55
forc build
must be modified to additionally:
~/.forc
directory (we only support Linux, and by coincidence macOS if it happens to work) or following rust-lang/rfcs#1615.--offline
flag: https://doc.rust-lang.org/cargo/commands/cargo-build.html#manifest-options.Tracked by #27
We need source mapping (going between Sway code and bytecode) to do debugging properly (e.g. set breakpoint at a certain line, continue up to a certain line), in addition to code coverage.
Add support for dumping source mapping to a defined file. It must be a file in order to be consumable easily without the need for inter-process communication.
build/source_mapping
)cc @adlerjohn for input
There needs to be some stdlib functionality wrapping contract calls in a safe way. There are a few options:
contract;
use std::chain::call;
let contract_result = call(hash, [args...], etc);
pros: relatively simple to understand
cons: the call
function would most likely be variadic, i.e. take different cardinalities of arguments. This would be a special case just for this function, since this isn't true in general about functions, and so it would be awkward in the compiler and the language spec.
ContractRequest
and ContractResponse
struct to interact with the api.contract;
use std::chain::call;
let req = ContractRequest {
to: <hash>, // contract id to call
inputs: Vec<param>,
outputs: Vec<param>,
};
let res: ContractResponse = call(req);
pros:
cons:
I lean towards option 2 right now. Feel free to respond with any other ideas you may have.
The ABI consists of two components: the function selector (see #3 for contract
intuitions and function selectors) and parameters. We will concern ourselves only with the parameters for now.
Ideally (but not necessarily), we want the ABI for parameters to exactly match the internal in-memory representation of data structures. That way, when a VM instance is initialized and the transaction data is placed in memory, it's trivial to make use of parameters since they have the same representation as any other structure.
For simple primitive types this is easy. but once we get into arrays and dynamically-allocated memory, things get more complicated.
Some concerns:
Vec
) store their payload on the heap, however this area of memory isn't directly available at VM initialization. We could put the payload somewhere in the transaction (e.g. immediately following the fixed-length part of the parameters), but then we'd need to know the offsets on transaction creation (which we can technically do).The other alternative is to have a different format than the internal memory representation, but that would require a (verifiable) translation step for each parameter.
One last concern is around safety: what if a user doesn't provide parameters is the expected format, or provides insufficient bytes? Solidity resolves this by having an infinite number of implicit zeroes following the end of parameters and using a separate ABI encoding for passing in untrusted data.
Major edit: the most reasonable approach IMO is for the ABI encoding to be equal to the internal memory layout. The process is as follows:
Re-entrancy attacks have been a huge plague on Ethereum smart contract development. Consider the following contract:
contract VulnerableContract {
mapping(address => uint256) balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount);
msg.sender.call.value(amount)();
balances[msg.sender] -= amount;
}
}
If msg.sender
is a contract, call
calls the contract. A maliciously-crafted contract can then call withdraw
again, and the require
will pass since balances
hasn't been updated. Repeating this potentially up to the block gas limit, the entire VulnerableContract
's balance can be drained.
This is resolved by the checks-effects-interactions pattern (more info: https://fravoll.github.io/solidity-patterns/checks_effects_interactions.html), where effects (writing to state) are applied before interactions (contract calls).
Detecting this should be quite easy in the compiler, which should have a flag (by default on and by default error) to automatically provide feedback to developers if they don't follow the checks-effects-interactions pattern. Developers that want to can disable it for parts of the code via a pragma.
forc fmt
doesn't format unless the project compiles, which is fine for now. However, the command-line forc fmt
command simply fails silently in that case. It should instead print the results of building, so the user can see that build failed.
As per @adlerjohn, the current language is lacking an address primitive.
John, would there be any inherent difference between byte32
and address
besides the stdlib methods implemented for them?
The ASM preamble for contracts' function selector needs to be written. This will probably go somewhere near the build_preamble
function here
The hashed function identifiers from #96 and the type of syntax tree would need to be passed in. This could either be in build_preamble
itself, or a nearby function that is only called on contract-type ASTs.
John suggests the formatter of the HLL by default should use tabs for indentation instead of spaces, configurable via an option.
Reason: tabs allow different people with different preferences and potentially impairments (e.g. a visual impairment that requires 8-width indentation to read code properly) to view the code however they like, without affecting the code. Spaces don't allow this, and otherwise offer no tangible benefit over tabs, other than playing slightly nicer with max-line-width formatting.
(The max-line-width argument doesn't rule in favor of spaces however. If someone likes 8-width indentation, with a max line of 80 chars they may end up with a max line of 84, or 88, or more chars (depending on the indentation level). Spaces don't fix this though, because if that same user made each indentation 8 spaces temporarily while working, they would end up in exactly the same situation!)
A counter-argument would be that modern languages have all gravitating towards using spaces and no one uses tabs anymore, but this is not true: Golang is both a modern language and probably the most popular language for building blockchain clients, and its default formatter uses tabs for indentation.
forc fmt
command to run format entirely from the command line. Ref #74.
The command should also accept a --check
parameter that doesn't format but only checks, allowing it to be used in CI.
Right now, libraries export everything. The grammar supports pub
as a keyword, and it is in the syntax tree, but it is not respected.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.