Using Sails, existing services can be combined into a new, unified service. This is achieved by using the extends
argument within the #[service]
attribute. For example, imagine you have two services: Service A and Service B. A new service, Service C, can be created that incorporates features from both Service A and Service B, as if they were originally designed to be part of a single service.
Let's take the existing fungible token service and extend it by adding new functionalities. The existing service already implements the standard token functionalities that must remain unchanged for any smart contract that is a token.
Code Explanation
The vft
crate contains the standard fungible token service (BaseVftService
).
An Events
enum is defined to handle custom events like Minted
and Burned
.
The VftService
struct implements an init
method to initialize the service with a token name, symbol, and decimals. It also includes a new
method which initializes VftService
using the default constructor of BaseVftService
without any parameters.
Using the #[service(extends = BaseVftService, events = Events)]
attribute, BaseVftService
is extended to add custom events to VftService
. This setup enables calling methods from BaseVftService
within VftService
and defining custom methods, like mint
and burn
.
The AsRef
trait is implemented for VftService
, referencing the embedded BaseVftService
instance.Implementing AsRef
allows easy access to the inner BaseVftService
when needed, but it doesn’t directly relate to utilizing methods from BaseVftService
within VftService
. The methods can be directly accessed through the vft
field without needing AsRef
.
Adding Admin Functionality:
To enhance the security and control of the token service, an administrator role is introduced. The administrator will have exclusive rights to perform critical actions such as minting and burning tokens.
Admin Storage and Access Control:
admin
field is added to the VftService
struct to store the administrator's address (ActorId
).static mut
variable, allowing for global access within the contract.set_admin
function is used to set or update the admin address. This function is called during the initialization of the contract to establish the initial admin and can later be used to change the admin via the change_admin
function.get_admin
function retrieves the current admin address. It is marked as unsafe
due to the use of a static mut
variable, but in the context of smart contracts on the Gear Protocol, where messages are executed sequentially, this approach is safe.only_admin
function is implemented to ensure that only the administrator can execute specific functions. This function compares the caller's address (gstd::msg::source()
) with the stored admin address. If the caller is not the admin, the transaction is aborted with an "Unauthorized access" error.#![no_std]
use sails_rs::prelude::*;
use vft::{Service as BaseVftService, Storage};
#[derive(Encode, Decode, TypeInfo)]
pub enum Events {
Minted { to: ActorId, value: U256 },
Burned { from: ActorId, value: U256 },
}
#[derive(Clone)]
pub struct VftService {
vft: BaseVftService,
}
static mut ADMIN: Option<ActorId> = None;
fn set_admin(admin: ActorId) {
unsafe {
ADMIN = Some(admin);
}
}
fn get_admin() -> &'static ActorId {
unsafe {
ADMIN.as_ref().expect("Contract is not initialized")
}
}
impl VftService {
pub fn init(name: String, symbol: String, decimals: u8) -> Self {
set_admin(admin);
VftService {
vft: <BaseVftService>::seed(name, symbol, decimals),
}
}
}
#[gservice(extends = BaseVftService, events = Events)]
impl VftService {
pub fn new() -> Self {
Self {
vft: BaseVftService::new(),
}
}
pub fn mint(&mut self, to: ActorId, value: U256) {
}
pub fn burn(&mut self, from: ActorId, value: U256) {
}
fn only_admin(&self) {
let admin = get_admin();
assert_eq!(*admin, gstd::msg::source(), "Unauthorized access");
}
}
impl AsRef<BaseVftService> for VftService {
fn as_ref(&self) -> &BaseVftService {
&self.vft
}
}
pub struct MyProgram;
#[program]
impl MyProgram {
pub fn new(name: String, symbol: String, decimals: u8) -> Self {
VftService::init(name, symbol, decimals);
Self
}
pub fn vft(&self) -> VftService {
VftService::new()
}
}
Using Sails, existing services can be combined into a new, unified service. This is achieved by using the extends
argument within the #[service]
attribute. For example, imagine you have two services: Service A and Service B. A new service, Service C, can be created that incorporates features from both Service A and Service B, as if they were originally designed to be part of a single service.
Let's take the existing fungible token service and extend it by adding new functionalities. The existing service already implements the standard token functionalities that must remain unchanged for any smart contract that is a token.
Code Explanation
The vft
crate contains the standard fungible token service (BaseVftService
).
An Events
enum is defined to handle custom events like Minted
and Burned
.
The VftService
struct implements an init
method to initialize the service with a token name, symbol, and decimals. It also includes a new
method which initializes VftService
using the default constructor of BaseVftService
without any parameters.
Using the #[service(extends = BaseVftService, events = Events)]
attribute, BaseVftService
is extended to add custom events to VftService
. This setup enables calling methods from BaseVftService
within VftService
and defining custom methods, like mint
and burn
.
The AsRef
trait is implemented for VftService
, referencing the embedded BaseVftService
instance.Implementing AsRef
allows easy access to the inner BaseVftService
when needed, but it doesn’t directly relate to utilizing methods from BaseVftService
within VftService
. The methods can be directly accessed through the vft
field without needing AsRef
.
Adding Admin Functionality:
To enhance the security and control of the token service, an administrator role is introduced. The administrator will have exclusive rights to perform critical actions such as minting and burning tokens.
Admin Storage and Access Control:
admin
field is added to the VftService
struct to store the administrator's address (ActorId
).static mut
variable, allowing for global access within the contract.set_admin
function is used to set or update the admin address. This function is called during the initialization of the contract to establish the initial admin and can later be used to change the admin via the change_admin
function.get_admin
function retrieves the current admin address. It is marked as unsafe
due to the use of a static mut
variable, but in the context of smart contracts on the Gear Protocol, where messages are executed sequentially, this approach is safe.only_admin
function is implemented to ensure that only the administrator can execute specific functions. This function compares the caller's address (gstd::msg::source()
) with the stored admin address. If the caller is not the admin, the transaction is aborted with an "Unauthorized access" error.#![no_std]
use sails_rs::prelude::*;
use vft::{Service as BaseVftService, Storage};
#[derive(Encode, Decode, TypeInfo)]
pub enum Events {
Minted { to: ActorId, value: U256 },
Burned { from: ActorId, value: U256 },
}
#[derive(Clone)]
pub struct VftService {
vft: BaseVftService,
}
static mut ADMIN: Option<ActorId> = None;
fn set_admin(admin: ActorId) {
unsafe {
ADMIN = Some(admin);
}
}
fn get_admin() -> &'static ActorId {
unsafe {
ADMIN.as_ref().expect("Contract is not initialized")
}
}
impl VftService {
pub fn init(name: String, symbol: String, decimals: u8) -> Self {
set_admin(admin);
VftService {
vft: <BaseVftService>::seed(name, symbol, decimals),
}
}
}
#[gservice(extends = BaseVftService, events = Events)]
impl VftService {
pub fn new() -> Self {
Self {
vft: BaseVftService::new(),
}
}
pub fn mint(&mut self, to: ActorId, value: U256) {
}
pub fn burn(&mut self, from: ActorId, value: U256) {
}
fn only_admin(&self) {
let admin = get_admin();
assert_eq!(*admin, gstd::msg::source(), "Unauthorized access");
}
}
impl AsRef<BaseVftService> for VftService {
fn as_ref(&self) -> &BaseVftService {
&self.vft
}
}
pub struct MyProgram;
#[program]
impl MyProgram {
pub fn new(name: String, symbol: String, decimals: u8) -> Self {
VftService::init(name, symbol, decimals);
Self
}
pub fn vft(&self) -> VftService {
VftService::new()
}
}