Skip to content

Commit 0706b1c

Browse files
authored
Merge branch 'master' into ironcev/abi-backtracing-trace-attribute
2 parents 7e6c4d2 + 384f46f commit 0706b1c

File tree

22 files changed

+312
-37
lines changed

22 files changed

+312
-37
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/book/spell-check-custom-words.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,3 +251,5 @@ Durations
251251
durations
252252
invariants
253253
postfix
254+
impl
255+
Impl

docs/book/src/sway-program-types/smart_contracts.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
A smart contract is no different than a script or predicate in that it is a piece of bytecode that is deployed to the blockchain via a [transaction](https://fuellabs.github.io/fuel-specs/master/protocol/tx_format). The main features of a smart contract that differentiate it from scripts or predicates are that it is _callable_ and _stateful_. Put another way, a smart contract is analogous to a deployed API with some database state.
66
<!-- contract:example:end -->
77

8-
The interface of a smart contract, also just called a contract, must be defined strictly with an [ABI declaration](#the-abi-declaration). See [this contract](../examples/wallet_smart_contract.md) for an example.
8+
The interface of a smart contract, also just called a contract, can be explicitly defined with an [ABI declaration](#the-abi-declaration) or implicitly with an [impl Self](#impl-self-contracts) item for the special type "Contract". See [this contract](../examples/wallet_smart_contract.md) for an example on using ABIs.
99

1010
## Syntax of a Smart Contract
1111

12-
As with any Sway program, the program starts with a declaration of what [program type](./index.md) it is. A contract must also either define or import an [ABI declaration](#the-abi-declaration) and implement it.
12+
As with any Sway program, the program starts with a declaration of what [program type](./index.md) it is. When using ABIs, a contract must either define or import an [ABI declaration](#the-abi-declaration) and implement it.
1313

1414
<!-- This section should explain best practices for ABIs -->
1515
<!-- ABI:example:start -->
@@ -100,3 +100,21 @@ Each special parameter is optional and assumes a default value when skipped:
100100
1. The default value for `gas` is the context gas (i.e. the content of the special register `$cgas`). Refer to the [FuelVM specifications](https://fuellabs.github.io/fuel-specs/master/vm) for more information about context gas.
101101
2. The default value for `coins` is 0.
102102
3. The default value for `asset_id` is `b256::zero()`.
103+
104+
## Impl Self Contracts
105+
106+
In some cases, it may be more convenient to avoid declaring an ABI and implement the contract directly, as shown in the example below.
107+
108+
Notice how there is no ABI specified in the `impl` item. In this case, the compiler will automatically create an ABI named as the package containing this `impl Contract` item, and will include each function in the ABI.
109+
110+
```sway
111+
{{#include ../../../../examples/wallet_smart_contract_self_impl/src/main.sw:abi_impl}}
112+
```
113+
114+
Without an ABI, there is no way for scripts and other contracts to use `abi(...)` and call this contract, but it can still be tested and called using any of available SDKs, as any other contract.
115+
116+
The ABI name will be the "upper camel case" version of the package name containing the "impl Contract" item. `CONTRACT_ID` is a compiler special constant that references the contract being implemented in this file.
117+
118+
```sway
119+
{{#include ../../../../examples/wallet_smart_contract_self_impl/src/main.sw:tests}}
120+
```

examples/Forc.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,8 @@ dependencies = [
194194
"std",
195195
"wallet_abi",
196196
]
197+
198+
[[package]]
199+
name = "wallet_smart_contract_self_impl"
200+
source = "member"
201+
dependencies = ["std"]

examples/Forc.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,5 @@ members = [
4141
"wallet_abi",
4242
"wallet_contract_caller_script",
4343
"wallet_smart_contract",
44+
"wallet_smart_contract_self_impl",
4445
]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[project]
2+
authors = ["Fuel Labs <[email protected]>"]
3+
entry = "main.sw"
4+
license = "Apache-2.0"
5+
name = "wallet_smart_contract_self_impl"
6+
7+
[dependencies]
8+
std = { path = "../../sway-lib-std" }
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// ANCHOR: full_wallet
2+
contract;
3+
4+
use std::{asset::transfer, call_frames::msg_asset_id, context::msg_amount};
5+
6+
const OWNER_ADDRESS = Address::from(0x8900c5bec4ca97d4febf9ceb4754a60d782abbf3cd815836c1872116f203f861);
7+
8+
storage {
9+
balance: u64 = 0,
10+
}
11+
12+
// ANCHOR: abi_impl
13+
impl Contract {
14+
#[storage(read, write), payable]
15+
fn receive_funds() {
16+
if msg_asset_id() == AssetId::base() {
17+
// If we received the base asset then keep track of the balance.
18+
// Otherwise, we're receiving other native assets and don't care
19+
// about our balance of coins.
20+
storage.balance.write(storage.balance.read() + msg_amount());
21+
}
22+
}
23+
24+
#[storage(read, write)]
25+
fn send_funds(amount_to_send: u64, recipient_address: Address) {
26+
let sender = msg_sender().unwrap();
27+
match sender {
28+
Identity::Address(addr) => assert(addr == OWNER_ADDRESS),
29+
_ => revert(0),
30+
};
31+
32+
let current_balance = storage.balance.read();
33+
assert(current_balance >= amount_to_send);
34+
35+
storage.balance.write(current_balance - amount_to_send);
36+
37+
// Note: `transfer()` is not a call and thus not an
38+
// interaction. Regardless, this code conforms to
39+
// checks-effects-interactions to avoid re-entrancy.
40+
transfer(
41+
Identity::Address(recipient_address),
42+
AssetId::base(),
43+
amount_to_send,
44+
);
45+
}
46+
}
47+
// ANCHOR_END: abi_impl
48+
49+
// ANCHOR: tests
50+
#[test]
51+
fn tests() {
52+
let w = abi(WalletSmartContractSelfImpl, CONTRACT_ID);
53+
w.receive_funds();
54+
}
55+
// ANCHOR_END: tests
56+
57+
// ANCHOR_END: full_wallet

sway-core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ sway-types.workspace = true
4848
sway-utils.workspace = true
4949
swayfmt.workspace = true
5050
thiserror.workspace = true
51+
toml = { workspace = true, features = ["parse"] }
5152
tracing.workspace = true
5253
uint.workspace = true
5354
vec1.workspace = true

sway-core/src/lib.rs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,17 @@ pub fn parse(
9898
engines: &Engines,
9999
config: Option<&BuildConfig>,
100100
experimental: ExperimentalFeatures,
101+
package_name: &str,
101102
) -> Result<(lexed::LexedProgram, parsed::ParseProgram), ErrorEmitted> {
102103
match config {
103-
None => parse_in_memory(handler, engines, src, experimental, DbgGeneration::None),
104+
None => parse_in_memory(
105+
handler,
106+
engines,
107+
src,
108+
experimental,
109+
DbgGeneration::None,
110+
package_name,
111+
),
104112
// When a `BuildConfig` is given,
105113
// the module source may declare `mod`s that must be parsed from other files.
106114
Some(config) => parse_module_tree(
@@ -114,6 +122,7 @@ pub fn parse(
114122
config.include_tests,
115123
experimental,
116124
config.lsp_mode.as_ref(),
125+
package_name,
117126
)
118127
.map(
119128
|ParsedModuleTree {
@@ -371,6 +380,7 @@ fn parse_in_memory(
371380
src: Source,
372381
experimental: ExperimentalFeatures,
373382
dbg_generation: DbgGeneration,
383+
package_name: &str,
374384
) -> Result<(lexed::LexedProgram, parsed::ParseProgram), ErrorEmitted> {
375385
let mut hasher = DefaultHasher::new();
376386
src.text.hash(&mut hasher);
@@ -385,7 +395,12 @@ fn parse_in_memory(
385395
let attributes_error_emitted = handler.append(attributes_handler);
386396

387397
let (kind, tree) = to_parsed_lang::convert_parse_tree(
388-
&mut to_parsed_lang::Context::new(BuildTarget::EVM, dbg_generation, experimental),
398+
&mut to_parsed_lang::Context::new(
399+
BuildTarget::EVM,
400+
dbg_generation,
401+
experimental,
402+
package_name,
403+
),
389404
handler,
390405
engines,
391406
module.value.clone(),
@@ -438,6 +453,7 @@ fn parse_submodules(
438453
include_tests: bool,
439454
experimental: ExperimentalFeatures,
440455
lsp_mode: Option<&LspConfig>,
456+
package_name: &str,
441457
) -> Submodules {
442458
// Assume the happy path, so there'll be as many submodules as dependencies, but no more.
443459
let mut submods = Vec::with_capacity(module.submodules().count());
@@ -471,6 +487,7 @@ fn parse_submodules(
471487
include_tests,
472488
experimental,
473489
lsp_mode,
490+
package_name,
474491
) {
475492
if !matches!(kind, parsed::TreeType::Library) {
476493
let source_id = engines.se().get_source_id(submod_path.as_ref());
@@ -525,6 +542,7 @@ fn parse_module_tree(
525542
include_tests: bool,
526543
experimental: ExperimentalFeatures,
527544
lsp_mode: Option<&LspConfig>,
545+
package_name: &str,
528546
) -> Result<ParsedModuleTree, ErrorEmitted> {
529547
let query_engine = engines.qe();
530548

@@ -546,6 +564,7 @@ fn parse_module_tree(
546564
include_tests,
547565
experimental,
548566
lsp_mode,
567+
package_name,
549568
);
550569

551570
let (attributes_handler, attributes) = attr_decls_to_attributes(
@@ -557,7 +576,7 @@ fn parse_module_tree(
557576

558577
// Convert from the raw parsed module to the `ParseTree` ready for type-check.
559578
let (kind, tree) = to_parsed_lang::convert_parse_tree(
560-
&mut to_parsed_lang::Context::new(build_target, dbg_generation, experimental),
579+
&mut to_parsed_lang::Context::new(build_target, dbg_generation, experimental, package_name),
561580
handler,
562581
engines,
563582
module.value.clone(),
@@ -1006,7 +1025,14 @@ pub fn compile_to_ast(
10061025
package_name,
10071026
"parse the program to a concrete syntax tree (CST)",
10081027
"parse_cst",
1009-
parse(src, handler, engines, build_config, experimental),
1028+
parse(
1029+
src,
1030+
handler,
1031+
engines,
1032+
build_config,
1033+
experimental,
1034+
package_name
1035+
),
10101036
build_config,
10111037
metrics
10121038
);
@@ -1521,6 +1547,7 @@ fn test_basic_prog() {
15211547
&engines,
15221548
None,
15231549
ExperimentalFeatures::default(),
1550+
"test",
15241551
);
15251552
prog.unwrap();
15261553
}
@@ -1541,6 +1568,7 @@ fn test_parenthesized() {
15411568
&engines,
15421569
None,
15431570
ExperimentalFeatures::default(),
1571+
"test",
15441572
);
15451573
prog.unwrap();
15461574
}
@@ -1563,6 +1591,7 @@ fn test_unary_ordering() {
15631591
&engines,
15641592
None,
15651593
ExperimentalFeatures::default(),
1594+
"test",
15661595
);
15671596
let (.., prog) = prog.unwrap();
15681597
// this should parse as `(!a) && b`, not `!(a && b)`. So, the top level
@@ -1612,6 +1641,7 @@ fn test_parser_recovery() {
16121641
&engines,
16131642
None,
16141643
ExperimentalFeatures::default(),
1644+
"test",
16151645
);
16161646
let (_, _) = prog.unwrap();
16171647
assert!(handler.has_errors());

sway-core/src/semantic_analysis/ast_node/declaration/auto_impl/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ where
160160
crate::BuildTarget::Fuel,
161161
dbg_generation,
162162
self.ctx.experimental,
163+
"", // this is only used for self impl contracts
163164
);
164165

165166
let handler = Handler::default();
@@ -236,6 +237,7 @@ where
236237
BuildTarget::Fuel,
237238
dbg_generation,
238239
self.ctx.experimental,
240+
"", // this is only used for self impl contracts
239241
);
240242

241243
let handler = Handler::default();

0 commit comments

Comments
 (0)