Scrypto component—how it is built
- Read firstly the docs from How to start section
- See how to set up local scrypto environment
- Read the general philosophy of the on-ledger component
- The full source of the component is available here
- See the docs of the RedStone Rust SDK—the component is built on
The info described there is mostly NOT REPEATED below.
Dependencies
- Use the following dependencies to embed RedStone Rust SDK into Scrypto.
[dependencies]
scrypto = { version = "1.3.0" }
redstone = { git = "https://github.com/redstone-finance/rust-sdk", tag = "2.0.0", default-features = false, features = ["radix"] }
Instantiating & Storage
Some of the values are necessary to be stored inside the component during its instantiating. Also, for the Push model, the values and timestamp are stored in the component.
#[blueprint]
mod price_adapter {
struct PriceAdapter {
signer_count_threshold: u8,
signers: Vec<Vec<u8>>,
prices: HashMap<Vec<u8>, RedStoneValue>,
timestamp: u64,
}
impl PriceAdapter {
pub fn instantiate(
signer_count_threshold: u8,
allowed_signer_addresses: Vec<Vec<u8>>,
) -> Global<PriceAdapter> {
let addresses: Vec<SignerAddress> = allowed_signer_addresses
.iter()
.map(|signer| (signer.clone()).into())
.collect();
verify_signers_config(addresses.as_slice(), signer_count_threshold)
.unwrap_or_else(|err| panic!("{}", err));
Self {
signer_count_threshold,
signers: allowed_signer_addresses,
prices: hashmap!(),
timestamp: 0,
}
.instantiate()
.prepare_to_globalize(OwnerRole::None)
.globalize()
}
}
}
Using the RedStone Rust SDK
Payload processing
- The payload bytes should be defined as described here.
- The payload can be generated as described here.
To process the payload data, the following command should be used inside the #[blueprint]
.
use redstone::{
core::{config::Config, ProcessorResult},
radix::RadixRedStoneConfig,
Value as RedStoneValue,
contract::verification::*,
SignerAddress
};
The function processes on-chain the payload passed as an argument and returns an array of aggregated values of each feed passed as an identifier inside feed_ids, and a timestamp related to the payload data packages.
fn process_payload(
&mut self,
feed_ids: Vec<Vec<u8>>,
payload: Vec<u8>,
) -> (u64, Vec<RedStoneValue>) {
let result = Self::try_process_payload(
Self::get_current_time(),
self.signer_count_threshold,
self.signers.clone(),
feed_ids,
payload,
None,
None,
)
.unwrap_or_else(|err| panic!("{}", err));
(result.timestamp.as_millis(), result.values)
}
fn try_process_payload(
block_timestamp: u64,
signer_count_threshold: u8,
signers: Vec<Vec<u8>>,
feed_ids: Vec<Vec<u8>>,
payload: Vec<u8>,
max_timestamp_delay_ms: Option<u64>,
max_timestamp_ahead_ms: Option<u64>,
) -> ProcessorResult {
let config: RadixRedStoneConfig = Config::try_new(
signer_count_threshold,
signers.into_iter().map(|id| id.into()).collect(),
feed_ids.into_iter().map(|id| id.into()).collect(),
block_timestamp.into(),
max_timestamp_delay_ms.map(|v| v.into()),
max_timestamp_ahead_ms.map(|v| v.into()),
)?
.into();
redstone::core::process_payload(&config, payload)
}
Config
The Config
structure is described here.
For safety reasons, the allowed signers
and signer_count_threshold
should be embedded in the component as defined above.
Current timestamp
Also, the current timestamp in milliseconds is necessary to be passed as the block timestamp
parameter:
use scrypto::prelude::*;
pub fn get_current_time() -> u64 {
let rtn = ScryptoVmV1Api::object_call(
CONSENSUS_MANAGER.as_node_id(),
CONSENSUS_MANAGER_GET_CURRENT_TIME_IDENT,
scrypto_encode(&ConsensusManagerGetCurrentTimeInputV2 {
precision: TimePrecisionV2::Second,
})
.unwrap(),
);
let instant: Instant = scrypto_decode(&rtn).unwrap();
instant.seconds_since_unix_epoch as u64
}
Errors
The possible errors thrown during the payload processing can be found here.
Pull model
To use the pull model, just invoke the process_payload
function and return the value.
pub fn get_prices(
&mut self,
feed_ids: Vec<Vec<u8>>,
payload: Vec<u8>,
) -> (u64, Vec<RedStoneValue>) {
self.process_payload(feed_ids, payload)
}
Push model
For the Push model, invoke the process_payload
function and save the value inside storage.
pub fn write_prices(
&mut self,
feed_ids: Vec<Vec<u8>>,
payload: Vec<u8>,
) -> (u64, Vec<RedStoneValue>) {
let (payload_timestamp, values) = self.process_payload(feed_ids.clone(), payload);
UpdateTimestampVerifier::Untrusted.verify_timestamp(
Self::get_current_time().into(),
None,
0.into(),
self.timestamp.into(),
payload_timestamp.into(),
).unwrap_or_else(|err| panic!("{}", err));
self.timestamp = payload_timestamp;
self.prices = feed_ids
.iter()
.zip(values.clone())
.map(|(feed_id, value)| (feed_id.clone(), value))
.collect();
(payload_timestamp, values)
}
Then the values can be read by using
pub fn read_prices(&mut self, feed_ids: Vec<Vec<u8>>) -> Vec<RedStoneValue> {
feed_ids
.iter()
.map(|feed_id| self.read_price(feed_id.clone()))
.collect()
}
or
pub fn read_price(&mut self, feed_id: Vec<u8>) -> RedStoneValue {
*self.prices.get(&feed_id).unwrap()
}