Skip to content

Commit

Permalink
Add checks to IR verification for invalid storage operations. (FuelLa…
Browse files Browse the repository at this point in the history
…bs#1716)

Each instruction within a function is inspected and if storage
operations are found they are confirmed to match the function `storage`
attribute annotation.
  • Loading branch information
otrho authored Jun 11, 2022
1 parent 560ca4b commit 9fd4bb8
Show file tree
Hide file tree
Showing 65 changed files with 1,587 additions and 758 deletions.
8 changes: 4 additions & 4 deletions examples/counter/src/main.sw
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
contract;

abi TestContract {
fn initialize_counter(value: u64) -> u64;
fn increment_counter(amount: u64) -> u64;
#[storage(write)]fn initialize_counter(value: u64) -> u64;
#[storage(read, write)]fn increment_counter(amount: u64) -> u64;
}

storage {
counter: u64,
}

impl TestContract for Contract {
fn initialize_counter(value: u64) -> u64 {
#[storage(write)]fn initialize_counter(value: u64) -> u64 {
storage.counter = value;
value
}

fn increment_counter(amount: u64) -> u64 {
#[storage(read, write)]fn increment_counter(amount: u64) -> u64 {
let incremented = storage.counter + amount;
storage.counter = incremented;
incremented
Expand Down
8 changes: 4 additions & 4 deletions examples/storage_example/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ contract;
use std::storage::{get, store};

abi StorageExample {
fn store_something(amount: u64);
fn get_something() -> u64;
#[storage(write)]fn store_something(amount: u64);
#[storage(read)]fn get_something() -> u64;
}

const STORAGE_KEY: b256 = 0x0000000000000000000000000000000000000000000000000000000000000000;

impl StorageExample for Contract {
fn store_something(amount: u64) {
#[storage(write)]fn store_something(amount: u64) {
store(STORAGE_KEY, amount);
}

fn get_something() -> u64 {
#[storage(read)]fn get_something() -> u64 {
let value = get::<u64>(STORAGE_KEY);
value
}
Expand Down
16 changes: 8 additions & 8 deletions examples/storage_map/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,29 @@ storage {
}

abi StorageMapExample {
fn insert_into_map1(key: u64, value: u64);
#[storage(write)]fn insert_into_map1(key: u64, value: u64);

fn get_from_map1(key: u64, value: u64);
#[storage(write)]fn get_from_map1(key: u64, value: u64);

fn insert_into_map2(key: (b256, bool), value: Data);
#[storage(read)]fn insert_into_map2(key: (b256, bool), value: Data);

fn get_from_map2(key: (b256, bool), value: Data);
#[storage(read)]fn get_from_map2(key: (b256, bool), value: Data);
}

impl StorageMapExample for Contract {
fn insert_into_map1(key: u64, value: u64) {
#[storage(write)]fn insert_into_map1(key: u64, value: u64) {
storage.map1.insert(key, value);
}

fn get_from_map1(key: u64, value: u64) {
#[storage(write)]fn get_from_map1(key: u64, value: u64) {
storage.map1.insert(key, value);
}

fn insert_into_map2(key: (b256, bool), value: Data) {
#[storage(read)]fn insert_into_map2(key: (b256, bool), value: Data) {
storage.map2.get(key);
}

fn get_from_map2(key: (b256, bool), value: Data) {
#[storage(read)]fn get_from_map2(key: (b256, bool), value: Data) {
storage.map2.get(key);
}
}
8 changes: 4 additions & 4 deletions examples/subcurrency/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ struct Sent {
abi Token {
// Mint new tokens and send to an address.
// Can only be called by the contract creator.
fn mint(receiver: Address, amount: u64);
#[storage(read, write)]fn mint(receiver: Address, amount: u64);

// Sends an amount of an existing token.
// Can be called from any address.
fn send(receiver: Address, amount: u64);
#[storage(read, write)]fn send(receiver: Address, amount: u64);
}

////////////////////////////////////////
Expand All @@ -67,7 +67,7 @@ storage {

/// Contract implements the `Token` ABI.
impl Token for Contract {
fn mint(receiver: Address, amount: u64) {
#[storage(read, write)]fn mint(receiver: Address, amount: u64) {
// Note: The return type of `msg_sender()` can be inferred by the
// compiler. It is shown here for explicitness.
let sender: Result<Identity, AuthError> = msg_sender();
Expand All @@ -85,7 +85,7 @@ impl Token for Contract {
storage.balances.insert(receiver, storage.balances.get(receiver) + amount)
}

fn send(receiver: Address, amount: u64) {
#[storage(read, write)]fn send(receiver: Address, amount: u64) {
// Note: The return type of `msg_sender()` can be inferred by the
// compiler. It is shown here for explicitness.
let sender: Result<Identity, AuthError> = msg_sender();
Expand Down
10 changes: 5 additions & 5 deletions examples/wallet_smart_contract/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,21 @@ storage {
}

abi Wallet {
fn receive_funds();
fn send_funds(amount_to_send: u64, recipient_address: Address);
#[storage(read, write)]fn receive_funds();
#[storage(read, write)]fn send_funds(amount_to_send: u64, recipient_address: Address);
}

impl Wallet for Contract {
fn receive_funds() {
#[storage(read, write)]fn receive_funds() {
if msg_asset_id() == ~ContractId::from(BASE_ASSET_ID) {
// If we received `BASE_ASSET_ID` then keep track of the balance.
// If we received `NATIVE_ASSET_ID` then keep track of the balance.
// Otherwise, we're receiving other native assets and don't care
// about our balance of tokens.
storage.balance = storage.balance + msg_amount();
}
}

fn send_funds(amount_to_send: u64, recipient_address: Address) {
#[storage(read, write)]fn send_funds(amount_to_send: u64, recipient_address: Address) {
// Note: The return type of `msg_sender()` can be inferred by the
// compiler. It is shown here for explicitness.
let sender: Result<Identity, AuthError> = msg_sender();
Expand Down
23 changes: 22 additions & 1 deletion sway-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,9 @@ pub enum Warning {
reg_name: Ident,
},
DeadStorageDeclaration,
DeadStorageDeclarationForFunction {
unneeded_attrib: String,
},
MatchExpressionUnreachableArm,
}

Expand Down Expand Up @@ -404,7 +407,14 @@ impl fmt::Display for Warning {
"This register declaration shadows the reserved register, \"{}\".",
reg_name
),
DeadStorageDeclaration => write!(f, "This storage declaration is never accessed and can be removed."
DeadStorageDeclaration => write!(
f,
"This storage declaration is never accessed and can be removed."
),
DeadStorageDeclarationForFunction { unneeded_attrib } => write!(
f,
"The '{unneeded_attrib}' storage declaration for this function is never accessed \
and can be removed."
),
MatchExpressionUnreachableArm => write!(f, "This match arm is unreachable."),
}
Expand Down Expand Up @@ -891,6 +901,16 @@ pub enum CompileError {
},
#[error("Impure function inside of non-contract. Contract storage is only accessible from contracts.")]
ImpureInNonContract { span: Span },
#[error(
"This function performs a storage {storage_op} but does not have the required \
attribute(s). Try adding \"#[{STORAGE_PURITY_ATTRIBUTE_NAME}({attrs})]\" to the function \
declaration."
)]
ImpureInPureContext {
storage_op: &'static str,
attrs: String,
span: Span,
},
#[error("Literal value is too large for type {ty}.")]
IntegerTooLarge { span: Span, ty: String },
#[error("Literal value underflows type {ty}.")]
Expand Down Expand Up @@ -1087,6 +1107,7 @@ impl Spanned for CompileError {
DeclIsNotAVariable { span, .. } => span.clone(),
DeclIsNotAnAbi { span, .. } => span.clone(),
ImpureInNonContract { span, .. } => span.clone(),
ImpureInPureContext { span, .. } => span.clone(),
IntegerTooLarge { span, .. } => span.clone(),
IntegerTooSmall { span, .. } => span.clone(),
IntegerContainsInvalidDigit { span, .. } => span.clone(),
Expand Down
47 changes: 32 additions & 15 deletions sway-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,30 +344,47 @@ pub(crate) fn compile_ast_to_ir_to_asm(
}
};

// Inline function calls since we don't support them yet. For scripts and predicates we inline
// into main(), and for contracts we inline into ABI impls, which are found due to them having
// a selector.
let mut functions_to_inline_to = Vec::new();
for (idx, fc) in &ir.functions {
if (matches!(tree_type, TreeType::Script | TreeType::Predicate)
&& fc.name == crate::constants::DEFAULT_ENTRY_POINT_FN_NAME)
|| (tree_type == TreeType::Contract && fc.selector.is_some())
{
functions_to_inline_to.push(::sway_ir::function::Function(idx));
}
// Find all the entry points. This is main for scripts and predicates, or ABI methods for
// contracts, identified by them having a selector.
let entry_point_functions: Vec<::sway_ir::Function> = ir
.functions
.iter()
.filter_map(|(idx, fc)| {
if (matches!(tree_type, TreeType::Script | TreeType::Predicate)
&& fc.name == crate::constants::DEFAULT_ENTRY_POINT_FN_NAME)
|| (tree_type == TreeType::Contract && fc.selector.is_some())
{
Some(::sway_ir::function::Function(idx))
} else {
None
}
})
.collect();

// Do a purity check on the _unoptimised_ IR.
let mut purity_checker = optimize::PurityChecker::default();
for entry_point in &entry_point_functions {
purity_checker.check_function(&ir, entry_point);
}
check!(
inline_function_calls(&mut ir, &functions_to_inline_to),
purity_checker.results(),
return err(warnings, errors),
warnings,
errors
);

// Inline function calls from the entry points.
check!(
inline_function_calls(&mut ir, &entry_point_functions),
return err(warnings, errors),
warnings,
errors
);

// The only other optimisation we have at the moment is constant combining. In lieu of a
// forthcoming pass manager we can just call it here now. We can re-use the inline functions
// list.
// forthcoming pass manager we can just call it here now.
check!(
combine_constants(&mut ir, &functions_to_inline_to),
combine_constants(&mut ir, &entry_point_functions),
return err(warnings, errors),
warnings,
errors
Expand Down
Loading

0 comments on commit 9fd4bb8

Please sign in to comment.