Skip to content

Commit e85b6ce

Browse files
adlerjohnnfurfaro
andauthored
Auth method 3 (#54)
* Add changes from prior work * Remove import of ops * Cleanup and format * Rename functions to match opcode naming * Re-order lib.sw * Fix types to use ContractId * Refactor and cleanup * Reorder lib.sw * Clean up comments and types * FIx lib deps ordering * cleanup * congig:update gitignore * Commit stashed changes with get_coin_owner WIP * Fix:cleanup merge conflict resolution mistake * Clean up a bit and use Result. * General cleanup. * Improve comment. * Fix a bunch of type errors. * Add missed assert. * Fix order to make compile. * Refactor. * fix * Fix build errors. * Update offsets for 255 max inputs. * Remove warnings. * Improve error name. * Fix some asm. * Fix more asm * space * Fix comment * Fix offsets. * Refactor to use new tx module. * test: add auth test project * test: register auth tests * test: add tests to mod.rs * test: add auth testing abi & contract * test: add auth caller contract & script * test: fix enum variant name * test: fix import of result * test: refactor types to workaround MissingData error from SDK * test: fix number of args passed * test: ignore script test * style: forc fmt * style: cargo fmt * test: work on script test * Remove redundant assert. * Clean up tests, not working yet. * Fix test for some reason. * Fix test. Co-authored-by: Nick Furfaro <nfurfaro33@gmail.com>
1 parent ae8def5 commit e85b6ce

File tree

21 files changed

+347
-25
lines changed

21 files changed

+347
-25
lines changed

src/assert.sw

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
library assert;
2+
3+
use ::panic::panic;
4+
5+
/// Assert that a value is true
6+
pub fn assert(a: bool) {
7+
if !a {
8+
panic(0);
9+
} else {
10+
()
11+
}
12+
}

src/chain.sw

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,3 @@ pub fn log_u8(val: u8) {
2727
log r1 zero zero zero;
2828
}
2929
}
30-
31-
/// Assert that a value is true
32-
pub fn assert(a: bool) {
33-
if !a {
34-
panic(0);
35-
} else {
36-
()
37-
}
38-
}

src/chain/auth.sw

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,98 @@
11
library auth;
2-
//! Functionality for determining who is calling an ABI method
2+
//! Functionality for determining who is calling a contract.
33

44
use ::address::Address;
5+
use ::assert::assert;
6+
use ::b512::B512;
57
use ::contract_id::ContractId;
8+
use ::option::*;
69
use ::result::Result;
10+
use ::tx::*;
711

812
pub enum AuthError {
9-
ContextError: (),
13+
InputsNotAllOwnedBySameAddress: (),
1014
}
1115

1216
pub enum Sender {
1317
Address: Address,
14-
Id: ContractId,
18+
ContractId: ContractId,
1519
}
1620

17-
/// Returns `true` if the caller is external (ie: a script or predicate).
18-
// ref: https://github.com/FuelLabs/fuel-specs/blob/master/specs/vm/opcodes.md#gm-get-metadata
21+
/// Returns `true` if the caller is external (i.e. a script).
22+
/// Otherwise, returns `false`.
23+
/// ref: https://github.com/FuelLabs/fuel-specs/blob/master/specs/vm/opcodes.md#gm-get-metadata
1924
pub fn caller_is_external() -> bool {
2025
asm(r1) {
2126
gm r1 i1;
2227
r1: bool
2328
}
2429
}
2530

26-
/// Get the `Sender` (ie: `Address`| ContractId) from which a call was made.
27-
/// Returns a Result::Ok(Sender) or Result::Error.
28-
// NOTE: Currently only returns Result::Ok variant if the parent context is Internal.
31+
/// If caller is internal, returns the contract ID of the caller.
32+
/// Otherwise, undefined behavior.
33+
pub fn caller_contract_id() -> ContractId {
34+
~ContractId::from(asm(r1) {
35+
gm r1 i2;
36+
r1: b256
37+
})
38+
}
39+
40+
/// Get the `Sender` (i.e. `Address` or `ContractId`) from which a call was made.
41+
/// Returns a `Result::Ok(Sender)`, or `Result::Err(AuthError)` if a sender cannot be determined.
2942
pub fn msg_sender() -> Result<Sender, AuthError> {
3043
if caller_is_external() {
31-
// TODO: Add call to get_coins_owner() here when implemented,
32-
Result::Err(AuthError::ContextError)
44+
let sender_res = get_coins_owner();
45+
if let Result::Ok(sender) = sender_res {
46+
Result::Ok(sender)
47+
} else {
48+
sender_res
49+
}
3350
} else {
34-
// Get caller's contract ID
35-
let id = ~ContractId::from(asm(r1) {
36-
gm r1 i2;
37-
r1: b256
38-
});
39-
Result::Ok(Sender::Id(id))
51+
// Get caller's `ContractId`.
52+
Result::Ok(Sender::ContractId(caller_contract_id()))
53+
}
54+
}
55+
56+
/// Get the owner of the inputs (of type `InputCoin`) to a TransactionScript,
57+
/// if they all share the same owner.
58+
fn get_coins_owner() -> Result<Sender, AuthError> {
59+
let target_input_type = 0u8;
60+
let inputs_count = tx_inputs_count();
61+
62+
let mut candidate = Option::None::<Address>();
63+
let mut i = 0u64;
64+
65+
while i < inputs_count {
66+
let input_pointer = tx_input_pointer(i);
67+
let input_type = tx_input_type(input_pointer);
68+
if input_type != target_input_type {
69+
// type != InputCoin
70+
// Continue looping.
71+
i = i + 1;
72+
} else {
73+
// type == InputCoin
74+
let input_owner = Option::Some(tx_input_coin_owner(input_pointer));
75+
if candidate.is_none() {
76+
// This is the first input seen of the correct type.
77+
candidate = input_owner;
78+
i = i + 1;
79+
} else {
80+
// Compare current coin owner to candidate.
81+
// `candidate` and `input_owner` must be `Option::Some` at this point,
82+
// so can unwrap safely.
83+
if input_owner.unwrap() == candidate.unwrap() {
84+
// Owners are a match, continue looping.
85+
i = i + 1;
86+
} else {
87+
// Owners don't match. Return Err.
88+
i = inputs_count;
89+
return Result::Err(AuthError::InputsNotAllOwnedBySameAddress);
90+
};
91+
};
92+
};
4093
}
94+
95+
// `candidate` must be `Option::Some` at this point, so can unwrap safely.
96+
// Note: `inputs_count` is guaranteed to be at least 1 for any valid tx.
97+
Result::Ok(Sender::Address(candidate.unwrap()))
4198
}

src/lib.sw

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
library std;
22

33
dep panic;
4+
dep assert;
45
dep option;
56
dep result;
67
dep constants;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
out
2+
target
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[project]
2+
authors = ["Fuel Labs <contact@fuel.sh>"]
3+
entry = "main.sw"
4+
license = "Apache-2.0"
5+
name = "auth_caller_contract"
6+
7+
[dependencies]
8+
std = { path = "../../../" }
9+
auth_testing_abi = { path = "../auth_testing_abi"}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
contract;
2+
3+
use auth_testing_abi::*;
4+
use std::contract_id::ContractId;
5+
use std::chain::auth::*;
6+
use std::constants::ZERO;
7+
use std::result::*;
8+
9+
abi AuthCaller {
10+
fn call_auth_contract(auth_id: ContractId, expected_id: ContractId) -> bool;
11+
}
12+
13+
impl AuthCaller for Contract {
14+
// TODO: improve this to return the ContractId itself.
15+
// This is a workaround for the MissingData("cannot parse custom type with no components") error
16+
fn call_auth_contract(auth_id: ContractId, expected_id: ContractId) -> bool {
17+
let auth_contract = abi(AuthTesting, ~ContractId::into(auth_id));
18+
auth_contract.returns_msg_sender(expected_id)
19+
}
20+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
out
2+
target
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[project]
2+
authors = ["Fuel Labs <contact@fuel.sh>"]
3+
entry = "main.sw"
4+
license = "Apache-2.0"
5+
name = "auth_caller_script"
6+
7+
[dependencies]
8+
std = { path = "../../../" }
9+
auth_testing_abi = { path = "../auth_testing_abi"}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
script;
2+
3+
use auth_testing_abi::AuthTesting;
4+
use std::contract_id::ContractId;
5+
use std::chain::auth::*;
6+
use std::result::*;
7+
8+
fn main() -> u64 {
9+
// TODO: ContractId for auth_testing_contract should ideally be passed to script as an arg when possible.
10+
let auth_contract = abi(AuthTesting, 0x377fd69456e97da7456331c18a859c9eb3ce741268c299eaea0167c0eff678ad);
11+
let auth_caller_contract = ~ContractId::from(0x2fc63a758319acb31e34cbc2853b5ae4068b81dacb674db15b0b6d8d7dac074a);
12+
let value = auth_contract.returns_msg_sender(auth_caller_contract);
13+
if !value {
14+
0
15+
} else {
16+
1
17+
}
18+
}

0 commit comments

Comments
 (0)