/
lib.rs
170 lines (155 loc) · 5.44 KB
/
lib.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//! # A smart contract for buying icecream safely
//!
//! This contract solves a very realistic problem related to icecream.
//! Imagine you want to purchase icecream from a vendor, and that you hate
//! eating icecream when it's raining. This contract solves the problem by
//! acting as a middleman which only allows your transfer to the icecream vendor
//! to go through if the sun is shining.
//!
//! The icecream contract relies on a weather service contract to determine the
//! weather. Both contracts are included in this module.
//!
//!
//! ## The Icecream Contract
//!
//! The contract is initialised with a contract address to the weather service
//! contract.
//!
//! Its primary function is `buy_icecream`, which works as follows:
//! - It is called with an `AccountAddress` of the icecream vendor and the
//! icecream price as amount.
//! - It queries the `Weather` from the weather_service contract.
//! - If it's `Weather::Sunny`, the transfer goes through to the icecream
//! vendor.
//! - Otherwise, the amount is returned to invoker.
//!
//! It also has a `replace_weather_service` function, in which the owner can
//! replace the weather service.
//!
//!
//! ## The Weather Service Contract
//!
//! The contract is initialised with the `Weather`.
//!
//! It has `get` and `set` receive functions, which either return or set the
//! weather. Only the owner can update the weather.
#![cfg_attr(not(feature = "std"), no_std)]
use concordium_std::*;
#[derive(Serialize, SchemaType, Clone)]
struct State {
weather_service: ContractAddress,
}
#[derive(Serialize, SchemaType, Clone, Copy, Debug)]
pub enum Weather {
Rainy,
Sunny,
}
/// The custom errors the contract can produce.
#[derive(Serialize, Debug, PartialEq, Eq, Reject, SchemaType)]
pub enum ContractError {
/// Failed parsing the parameter.
#[from(ParseError)]
ParseParams,
/// Failed account transfer.
#[from(TransferError)]
TransferError,
/// Failed contract invoke.
ContractInvokeError,
Unauthenticated,
}
impl<A> From<CallContractError<A>> for ContractError {
fn from(_: CallContractError<A>) -> Self { Self::ContractInvokeError }
}
type ContractResult<A> = Result<A, ContractError>;
/// Initialise the contract with the contract address of the weather service.
#[init(contract = "icecream", parameter = "ContractAddress")]
fn contract_init(ctx: &InitContext, _state_builder: &mut StateBuilder) -> InitResult<State> {
let weather_service: ContractAddress = ctx.parameter_cursor().get()?;
Ok(State {
weather_service,
})
}
/// Attempt purchasing icecream from the icecream vendor.
#[receive(
contract = "icecream",
name = "buy_icecream",
parameter = "AccountAddress",
payable,
mutable,
error = "ContractError"
)]
fn contract_buy_icecream(
ctx: &ReceiveContext,
host: &mut Host<State>,
amount: Amount,
) -> ContractResult<()> {
let weather_service = host.state().weather_service;
let icecream_vendor: AccountAddress = ctx.parameter_cursor().get()?;
concordium_dbg!("vendor = {icecream_vendor:?}");
let weather = host
.invoke_contract_raw(
&weather_service,
Parameter::empty(),
EntrypointName::new_unchecked("get"),
Amount::zero(),
)?
.1;
let weather = if let Some(mut weather) = weather {
weather.get()?
} else {
return Err(ContractError::ContractInvokeError);
};
concordium_dbg!("Got weather = {weather:?}");
match weather {
Weather::Rainy => {
host.invoke_transfer(&ctx.invoker(), amount)?;
// We could also abort here, but this is useful to show off some
// testing features.
}
Weather::Sunny => host.invoke_transfer(&icecream_vendor, amount)?,
}
Ok(())
}
/// Replace the weather service with another.
/// Only the owner of the contract can do so.
#[receive(
contract = "icecream",
name = "replace_weather_service",
parameter = "ContractAddress",
mutable,
error = "ContractError"
)]
fn contract_replace_weather_service(
ctx: &ReceiveContext,
host: &mut Host<State>,
) -> ContractResult<()> {
ensure_eq!(Address::Account(ctx.owner()), ctx.sender(), ContractError::Unauthenticated);
let new_weather_service: ContractAddress = ctx.parameter_cursor().get()?;
host.state_mut().weather_service = new_weather_service;
Ok(())
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Initialise the weather service with the weather.
#[init(contract = "weather", parameter = "Weather")]
fn weather_init(ctx: &InitContext, _state_builder: &mut StateBuilder) -> InitResult<Weather> {
let weather = ctx.parameter_cursor().get()?;
Ok(weather)
}
/// Get the current weather.
#[receive(contract = "weather", name = "get", return_value = "Weather", error = "ContractError")]
fn weather_get(_ctx: &ReceiveContext, host: &Host<Weather>) -> ContractResult<Weather> {
Ok(*host.state())
}
/// Update the weather.
#[receive(
contract = "weather",
name = "set",
parameter = "Weather",
mutable,
error = "ContractError"
)]
fn weather_set(ctx: &ReceiveContext, host: &mut ExternHost<Weather>) -> ContractResult<()> {
ensure_eq!(Address::Account(ctx.owner()), ctx.sender(), ContractError::Unauthenticated); // Only the owner can update the weather.
*host.state_mut() = ctx.parameter_cursor().get()?;
Ok(())
}