Skip to main content

ore_api/state/
automation.rs

1use ore_mint_api::consts::ONE_ORE;
2use serde::{Deserialize, Serialize};
3use steel::*;
4
5use crate::state::{automation_pda, OreAccount};
6
7#[repr(C)]
8#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, Serialize, Deserialize)]
9pub struct Automation {
10    /// The amount of SOL to deploy on each territory per round.
11    pub amount: u64,
12
13    /// The authority of this automation account.
14    pub authority: Pubkey,
15
16    /// The amount of SOL this automation has left.
17    pub balance: u64,
18
19    /// The executor of this automation account.
20    pub executor: Pubkey,
21
22    /// The amount of SOL the executor should receive in fees.
23    pub fee: u64,
24
25    /// The strategy this automation uses.
26    pub strategy: u64,
27
28    /// The mask of squares this automation should deploy to if preferred strategy.
29    /// If strategy is Random, first bit is used to determine how many squares to deploy to.
30    pub mask: u64,
31
32    /// Whether or not to auto-reload SOL winnings into the automation balance.
33    pub reload: u64,
34
35    /// The total SOL spent (lost to fees) by this automation.
36    pub total_sol_spent: u64,
37
38    /// The total ORE earned by this automation.
39    pub total_ore_earned: u64,
40
41    /// Conditions that must be met for the automation to deploy.
42    pub conditions: AutomationConditions,
43}
44
45/// Conditions that gate whether an automation deploys in a given round.
46#[repr(C)]
47#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, Serialize, Deserialize)]
48pub struct AutomationConditions {
49    /// Max production cost EMA (lamports per whole ORE). Deploy blocked if EMA exceeds this.
50    /// Default: u64::MAX (no upper bound).
51    pub max_production_cost: u64,
52
53    /// Min motherlode amount (whole ORE units). Deploy blocked if motherlode is below this.
54    /// Default: 0 (no lower bound).
55    pub min_motherlode: u16,
56
57    /// Max motherlode amount (whole ORE units). Deploy blocked if motherlode exceeds this.
58    /// Default: u64::MAX (no upper bound).
59    pub max_motherlode: u16,
60
61    /// Number of split tiles to target.
62    /// Default: u16::MAX (no preference).
63    pub split_tiles: u16,
64
65    /// Number of solo tiles to target.
66    /// Default: u16::MAX (no preference).
67    pub solo_tiles: u16,
68
69    /// Unused buffer space.
70    pub _buffer: u64,
71}
72
73#[repr(u8)]
74#[derive(Clone, Copy, Debug, PartialEq, IntoPrimitive, TryFromPrimitive)]
75pub enum AutomationStrategy {
76    Random = 0,
77    Preferred = 1,
78    Discretionary = 2,
79    DiscretionaryBps = 3,
80}
81
82impl AutomationStrategy {
83    pub fn from_u64(value: u64) -> Self {
84        Self::try_from(value as u8).unwrap()
85    }
86}
87
88impl Default for AutomationConditions {
89    fn default() -> Self {
90        Self {
91            max_production_cost: u64::MAX,
92            min_motherlode: 0,
93            max_motherlode: u16::MAX,
94            split_tiles: 0,
95            solo_tiles: 0,
96            _buffer: 0,
97        }
98    }
99}
100
101impl AutomationConditions {
102    pub fn to_bytes(&self) -> [u8; 24] {
103        let mut bytes = [0; 24];
104        bytes[0..8].copy_from_slice(&self.max_production_cost.to_le_bytes());
105        bytes[8..10].copy_from_slice(&self.min_motherlode.to_le_bytes());
106        bytes[10..12].copy_from_slice(&self.max_motherlode.to_le_bytes());
107        bytes[12..14].copy_from_slice(&self.split_tiles.to_le_bytes());
108        bytes[14..16].copy_from_slice(&self.solo_tiles.to_le_bytes());
109        bytes[16..24].copy_from_slice(&self._buffer.to_le_bytes());
110        bytes
111    }
112
113    pub fn from_bytes(bytes: [u8; 24]) -> Self {
114        Self {
115            max_production_cost: u64::from_le_bytes(bytes[0..8].try_into().unwrap()),
116            min_motherlode: u16::from_le_bytes(bytes[8..10].try_into().unwrap()),
117            max_motherlode: u16::from_le_bytes(bytes[10..12].try_into().unwrap()),
118            split_tiles: u16::from_le_bytes(bytes[12..14].try_into().unwrap()),
119            solo_tiles: u16::from_le_bytes(bytes[14..16].try_into().unwrap()),
120            _buffer: u64::from_le_bytes(bytes[16..24].try_into().unwrap()),
121        }
122    }
123}
124
125impl Automation {
126    pub fn pda(&self) -> (Pubkey, u8) {
127        automation_pda(self.authority)
128    }
129
130    pub fn min_fee(&self, deploy_amount: u64) -> u64 {
131        if self.strategy == AutomationStrategy::DiscretionaryBps as u64 {
132            ((deploy_amount as u128 * self.fee as u128) / crate::consts::DENOMINATOR_BPS as u128) as u64
133        } else {
134            self.fee
135        }
136    }
137
138    pub fn production_cost(&self) -> u64 {
139        if self.total_ore_earned == 0 {
140            return 0;
141        }
142        ((self.total_sol_spent as u128) * (ONE_ORE as u128) / (self.total_ore_earned as u128))
143            as u64
144    }
145}
146
147account!(OreAccount, Automation);