|
1 | 1 | library auth; |
2 | | -//! Functionality for determining who is calling an ABI method |
| 2 | +//! Functionality for determining who is calling a contract. |
3 | 3 |
|
4 | 4 | use ::address::Address; |
| 5 | +use ::assert::assert; |
| 6 | +use ::b512::B512; |
5 | 7 | use ::contract_id::ContractId; |
| 8 | +use ::option::*; |
6 | 9 | use ::result::Result; |
| 10 | +use ::tx::*; |
7 | 11 |
|
8 | 12 | pub enum AuthError { |
9 | | - ContextError: (), |
| 13 | + InputsNotAllOwnedBySameAddress: (), |
10 | 14 | } |
11 | 15 |
|
12 | 16 | pub enum Sender { |
13 | 17 | Address: Address, |
14 | | - Id: ContractId, |
| 18 | + ContractId: ContractId, |
15 | 19 | } |
16 | 20 |
|
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 |
19 | 24 | pub fn caller_is_external() -> bool { |
20 | 25 | asm(r1) { |
21 | 26 | gm r1 i1; |
22 | 27 | r1: bool |
23 | 28 | } |
24 | 29 | } |
25 | 30 |
|
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. |
29 | 42 | pub fn msg_sender() -> Result<Sender, AuthError> { |
30 | 43 | 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 | + } |
33 | 50 | } 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 | + }; |
40 | 93 | } |
| 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())) |
41 | 98 | } |
0 commit comments