├── .gitignore ├── Move.toml ├── LICENSE └── sources ├── birthday_bot.move └── birthday_bot_test.move /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | storage/ 3 | .idea/ 4 | .aptos/ -------------------------------------------------------------------------------- /Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "birthday_bot" 3 | version = "1.0.0" 4 | 5 | [addresses] 6 | std = "0x1" 7 | overmind = "0x1337" 8 | 9 | [dependencies] 10 | MoveStdlib = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/move-stdlib", rev = "093b497d1267715a222845aad4fd3ca59da90e8d" } 11 | AptosFramework = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-framework", rev = "093b497d1267715a222845aad4fd3ca59da90e8d" } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Overmind 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /sources/birthday_bot.move: -------------------------------------------------------------------------------- 1 | module overmind::birthday_bot { 2 | use aptos_std::table::Table; 3 | use std::signer; 4 | use std::error; 5 | use aptos_framework::account; 6 | use std::vector; 7 | use aptos_framework::coin; 8 | use aptos_framework::aptos_coin::AptosCoin; 9 | use aptos_std::table; 10 | use aptos_framework::timestamp; 11 | 12 | // 13 | // Errors 14 | // 15 | const ERROR_DISTRIBUTION_STORE_EXIST: u64 = 0; 16 | const ERROR_DISTRIBUTION_STORE_DOES_NOT_EXIST: u64 = 1; 17 | const ERROR_LENGTHS_NOT_EQUAL: u64 = 2; 18 | const ERROR_BIRTHDAY_GIFT_DOES_NOT_EXIST: u64 = 3; 19 | const ERROR_BIRTHDAY_TIMESTAMP_SECONDS_HAS_NOT_PASSED: u64 = 4; 20 | 21 | // 22 | // Data structures 23 | // 24 | struct BirthdayGift has drop, store { 25 | amount: u64, 26 | birthday_timestamp_seconds: u64, 27 | } 28 | 29 | struct DistributionStore has key { 30 | birthday_gifts: Table, 31 | signer_capability: account::SignerCapability, 32 | } 33 | 34 | // 35 | // Assert functions 36 | // 37 | public fun assert_distribution_store_exists( 38 | account_address: address, 39 | ) { 40 | // TODO: assert that `DistributionStore` exists 41 | } 42 | 43 | public fun assert_distribution_store_does_not_exist( 44 | account_address: address, 45 | ) { 46 | // TODO: assert that `DistributionStore` does not exist 47 | } 48 | 49 | public fun assert_lengths_are_equal( 50 | addresses: vector
, 51 | amounts: vector, 52 | timestamps: vector 53 | ) { 54 | // TODO: assert that the lengths of `addresses`, `amounts`, and `timestamps` are all equal 55 | } 56 | 57 | public fun assert_birthday_gift_exists( 58 | distribution_address: address, 59 | address: address, 60 | ) acquires DistributionStore { 61 | // TODO: assert that `birthday_gifts` exists 62 | } 63 | 64 | public fun assert_birthday_timestamp_seconds_has_passed( 65 | distribution_address: address, 66 | address: address, 67 | ) acquires DistributionStore { 68 | // TODO: assert that the current timestamp is greater than or equal to `birthday_timestamp_seconds` 69 | } 70 | 71 | // 72 | // Entry functions 73 | // 74 | /** 75 | * Initializes birthday gift distribution contract 76 | * @param account - account signer executing the function 77 | * @param addresses - list of addresses that can claim their birthday gifts 78 | * @param amounts - list of amounts for birthday gifts 79 | * @param birthday_timestamps - list of birthday timestamps in seconds (only claimable after this timestamp has passed) 80 | **/ 81 | public entry fun initialize_distribution( 82 | account: &signer, 83 | addresses: vector
, 84 | amounts: vector, 85 | birthday_timestamps: vector 86 | ) { 87 | // TODO: check `DistributionStore` does not exist 88 | 89 | // TODO: check all lengths of `addresses`, `amounts`, and `birthday_timestamps` are equal 90 | 91 | // TODO: create resource account 92 | 93 | // TODO: register Aptos coin to resource account 94 | 95 | // TODO: loop through the lists and push items to birthday_gifts table 96 | 97 | // TODO: transfer the sum of all items in `amounts` from initiator to resource account 98 | 99 | // TODO: move_to resource `DistributionStore` to account signer 100 | } 101 | 102 | /** 103 | * Add birthday gift to `DistributionStore.birthday_gifts` 104 | * @param account - account signer executing the function 105 | * @param address - address that can claim the birthday gift 106 | * @param amount - amount for the birthday gift 107 | * @param birthday_timestamp_seconds - birthday timestamp in seconds (only claimable after this timestamp has passed) 108 | **/ 109 | public entry fun add_birthday_gift( 110 | account: &signer, 111 | address: address, 112 | amount: u64, 113 | birthday_timestamp_seconds: u64 114 | ) acquires DistributionStore { 115 | // TODO: check that the distribution store exists 116 | 117 | // TODO: set new birthday gift to new `amount` and `birthday_timestamp_seconds` (birthday_gift already exists, sum `amounts` and override the `birthday_timestamp_seconds` 118 | 119 | // TODO: transfer the `amount` from initiator to resource account 120 | } 121 | 122 | /** 123 | * Remove birthday gift from `DistributionStore.birthday_gifts` 124 | * @param account - account signer executing the function 125 | * @param address - `birthday_gifts` address 126 | **/ 127 | public entry fun remove_birthday_gift( 128 | account: &signer, 129 | address: address, 130 | ) acquires DistributionStore { 131 | // TODO: check that the distribution store exists 132 | 133 | // TODO: if `birthday_gifts` exists, remove `birthday_gift` from table and transfer `amount` from resource account to initiator 134 | } 135 | 136 | /** 137 | * Claim birthday gift from `DistributionStore.birthday_gifts` 138 | * @param account - account signer executing the function 139 | * @param distribution_address - distribution contract address 140 | **/ 141 | public entry fun claim_birthday_gift( 142 | account: &signer, 143 | distribution_address: address, 144 | ) acquires DistributionStore { 145 | // TODO: check that the distribution store exists 146 | 147 | // TODO: check that the `birthday_gift` exists 148 | 149 | // TODO: check that the `birthday_timestamp_seconds` has passed 150 | 151 | // TODO: remove `birthday_gift` from table and transfer `amount` from resource account to initiator 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /sources/birthday_bot_test.move: -------------------------------------------------------------------------------- 1 | module overmind::birthday_bot_test { 2 | #[test_only] 3 | use aptos_framework::timestamp; 4 | #[test_only] 5 | use std::signer; 6 | #[test_only] 7 | use aptos_framework::account; 8 | #[test_only] 9 | use aptos_framework::coin; 10 | #[test_only] 11 | use aptos_framework::aptos_coin::AptosCoin; 12 | #[test_only] 13 | use aptos_framework::aptos_coin; 14 | #[test_only] 15 | use std::vector; 16 | #[test_only] 17 | use overmind::birthday_bot::{initialize_distribution, assert_distribution_store_exists, add_birthday_gift, remove_birthday_gift, claim_birthday_gift}; 18 | 19 | // 20 | // Test functions 21 | // Searching == test_[function_name]_[success || failture]_[reason] 22 | // 23 | #[test(aptos_framework = @0x1, account = @0xCAFE, test_one = @0x12, test_two = @0x34)] 24 | fun test_initialize_distribution_success( 25 | aptos_framework: &signer, 26 | account: &signer, 27 | test_one: &signer, 28 | test_two: &signer 29 | ) { 30 | timestamp::set_time_has_started_for_testing(aptos_framework); 31 | let (burn_cap, mint_cap) = aptos_framework::aptos_coin::initialize_for_test(aptos_framework); 32 | 33 | let aptos_framework_address = signer::address_of(aptos_framework); 34 | let account_address = signer::address_of(account); 35 | let test_one_address = signer::address_of(test_one); 36 | let test_two_address = signer::address_of(test_two); 37 | 38 | account::create_account_for_test(aptos_framework_address); 39 | account::create_account_for_test(account_address); 40 | 41 | coin::register(account); 42 | aptos_coin::mint(aptos_framework, account_address, 3000000); 43 | 44 | let addresses: vector
= vector::empty(); 45 | let amounts: vector = vector::empty(); 46 | let birthday_timestamps: vector = vector::empty(); 47 | 48 | vector::push_back(&mut addresses, test_one_address); 49 | vector::push_back(&mut addresses, test_two_address); 50 | 51 | vector::push_back(&mut amounts, 1000000); 52 | vector::push_back(&mut amounts, 2000000); 53 | 54 | vector::push_back(&mut birthday_timestamps, timestamp::now_seconds()); 55 | vector::push_back(&mut birthday_timestamps, timestamp::now_seconds()); 56 | 57 | initialize_distribution( 58 | account, 59 | addresses, 60 | amounts, 61 | birthday_timestamps 62 | ); 63 | 64 | assert_distribution_store_exists(account_address); 65 | assert!(coin::balance(account_address) == 0, 0); 66 | 67 | coin::destroy_burn_cap(burn_cap); 68 | coin::destroy_mint_cap(mint_cap); 69 | } 70 | 71 | #[test(aptos_framework = @0x1, account = @0xCAFE, test_one = @0x12, test_two = @0x34)] 72 | #[expected_failure(abort_code = 196608, location = overmind::birthday_bot)] 73 | fun test_initialize_distribution_failure_distribution_store_already_exists( 74 | aptos_framework: &signer, 75 | account: &signer, 76 | test_one: &signer, 77 | test_two: &signer 78 | ) { 79 | timestamp::set_time_has_started_for_testing(aptos_framework); 80 | let (burn_cap, mint_cap) = aptos_framework::aptos_coin::initialize_for_test(aptos_framework); 81 | 82 | let aptos_framework_address = signer::address_of(aptos_framework); 83 | let account_address = signer::address_of(account); 84 | let test_one_address = signer::address_of(test_one); 85 | let test_two_address = signer::address_of(test_two); 86 | 87 | account::create_account_for_test(aptos_framework_address); 88 | account::create_account_for_test(account_address); 89 | 90 | coin::register(account); 91 | aptos_coin::mint(aptos_framework, account_address, 3000000); 92 | 93 | let addresses: vector
= vector::empty(); 94 | let amounts: vector = vector::empty(); 95 | let birthday_timestamps: vector = vector::empty(); 96 | 97 | vector::push_back(&mut addresses, test_one_address); 98 | vector::push_back(&mut addresses, test_two_address); 99 | 100 | vector::push_back(&mut amounts, 1000000); 101 | vector::push_back(&mut amounts, 2000000); 102 | 103 | vector::push_back(&mut birthday_timestamps, timestamp::now_seconds()); 104 | vector::push_back(&mut birthday_timestamps, timestamp::now_seconds()); 105 | 106 | initialize_distribution( 107 | account, 108 | addresses, 109 | amounts, 110 | birthday_timestamps 111 | ); 112 | 113 | initialize_distribution( 114 | account, 115 | addresses, 116 | amounts, 117 | birthday_timestamps 118 | ); 119 | 120 | coin::destroy_burn_cap(burn_cap); 121 | coin::destroy_mint_cap(mint_cap); 122 | } 123 | 124 | #[test(aptos_framework = @0x1, account = @0xCAFE, test_one = @0x12, test_two = @0x34)] 125 | #[expected_failure(abort_code = 196610, location = overmind::birthday_bot)] 126 | fun test_initialize_distribution_failure_not_equal_lengths( 127 | aptos_framework: &signer, 128 | account: &signer, 129 | test_one: &signer, 130 | test_two: &signer 131 | ) { 132 | timestamp::set_time_has_started_for_testing(aptos_framework); 133 | let (burn_cap, mint_cap) = aptos_framework::aptos_coin::initialize_for_test(aptos_framework); 134 | 135 | let aptos_framework_address = signer::address_of(aptos_framework); 136 | let account_address = signer::address_of(account); 137 | let test_one_address = signer::address_of(test_one); 138 | let test_two_address = signer::address_of(test_two); 139 | 140 | account::create_account_for_test(aptos_framework_address); 141 | account::create_account_for_test(account_address); 142 | 143 | coin::register(account); 144 | aptos_coin::mint(aptos_framework, account_address, 3000000); 145 | 146 | let addresses: vector
= vector::empty(); 147 | let amounts: vector = vector::empty(); 148 | let birthday_timestamps: vector = vector::empty(); 149 | 150 | vector::push_back(&mut addresses, test_one_address); 151 | vector::push_back(&mut addresses, test_two_address); 152 | 153 | vector::push_back(&mut amounts, 1000000); 154 | vector::push_back(&mut amounts, 2000000); 155 | 156 | vector::push_back(&mut birthday_timestamps, timestamp::now_seconds()); 157 | vector::push_back(&mut birthday_timestamps, timestamp::now_seconds()); 158 | vector::push_back(&mut birthday_timestamps, timestamp::now_seconds()); 159 | 160 | initialize_distribution( 161 | account, 162 | addresses, 163 | amounts, 164 | birthday_timestamps 165 | ); 166 | 167 | coin::destroy_burn_cap(burn_cap); 168 | coin::destroy_mint_cap(mint_cap); 169 | } 170 | 171 | #[test(aptos_framework = @0x1, account = @0xCAFE, test_one = @0x12, test_two = @0x34, test_three = @0x56)] 172 | fun test_add_birthday_gift_success( 173 | aptos_framework: &signer, 174 | account: &signer, 175 | test_one: &signer, 176 | test_two: &signer, 177 | test_three: &signer 178 | ) { 179 | timestamp::set_time_has_started_for_testing(aptos_framework); 180 | let (burn_cap, mint_cap) = aptos_framework::aptos_coin::initialize_for_test(aptos_framework); 181 | 182 | let aptos_framework_address = signer::address_of(aptos_framework); 183 | let account_address = signer::address_of(account); 184 | let test_one_address = signer::address_of(test_one); 185 | let test_two_address = signer::address_of(test_two); 186 | let test_three_address = signer::address_of(test_three); 187 | 188 | account::create_account_for_test(aptos_framework_address); 189 | account::create_account_for_test(account_address); 190 | 191 | coin::register(account); 192 | aptos_coin::mint(aptos_framework, account_address, 4000000); 193 | 194 | let addresses: vector
= vector::empty(); 195 | let amounts: vector = vector::empty(); 196 | let birthday_timestamps: vector = vector::empty(); 197 | 198 | vector::push_back(&mut addresses, test_one_address); 199 | vector::push_back(&mut addresses, test_two_address); 200 | 201 | vector::push_back(&mut amounts, 1000000); 202 | vector::push_back(&mut amounts, 2000000); 203 | 204 | vector::push_back(&mut birthday_timestamps, timestamp::now_seconds()); 205 | vector::push_back(&mut birthday_timestamps, timestamp::now_seconds()); 206 | 207 | initialize_distribution( 208 | account, 209 | addresses, 210 | amounts, 211 | birthday_timestamps 212 | ); 213 | 214 | add_birthday_gift(account, test_three_address, 1000000, timestamp::now_seconds()); 215 | 216 | assert!(coin::balance(account_address) == 0, 0); 217 | 218 | coin::destroy_burn_cap(burn_cap); 219 | coin::destroy_mint_cap(mint_cap); 220 | } 221 | 222 | #[test(aptos_framework = @0x1, account = @0xCAFE, test_one = @0x12, test_two = @0x34)] 223 | fun test_remove_birthday_gift_success( 224 | aptos_framework: &signer, 225 | account: &signer, 226 | test_one: &signer, 227 | test_two: &signer, 228 | ) { 229 | timestamp::set_time_has_started_for_testing(aptos_framework); 230 | let (burn_cap, mint_cap) = aptos_framework::aptos_coin::initialize_for_test(aptos_framework); 231 | 232 | let aptos_framework_address = signer::address_of(aptos_framework); 233 | let account_address = signer::address_of(account); 234 | let test_one_address = signer::address_of(test_one); 235 | let test_two_address = signer::address_of(test_two); 236 | 237 | account::create_account_for_test(aptos_framework_address); 238 | account::create_account_for_test(account_address); 239 | 240 | coin::register(account); 241 | aptos_coin::mint(aptos_framework, account_address, 3000000); 242 | 243 | let addresses: vector
= vector::empty(); 244 | let amounts: vector = vector::empty(); 245 | let birthday_timestamps: vector = vector::empty(); 246 | 247 | vector::push_back(&mut addresses, test_one_address); 248 | vector::push_back(&mut addresses, test_two_address); 249 | 250 | vector::push_back(&mut amounts, 1000000); 251 | vector::push_back(&mut amounts, 2000000); 252 | 253 | vector::push_back(&mut birthday_timestamps, timestamp::now_seconds()); 254 | vector::push_back(&mut birthday_timestamps, timestamp::now_seconds()); 255 | 256 | initialize_distribution( 257 | account, 258 | addresses, 259 | amounts, 260 | birthday_timestamps 261 | ); 262 | 263 | remove_birthday_gift(account, test_two_address); 264 | 265 | assert!(coin::balance(account_address) == 2000000, 0); 266 | 267 | coin::destroy_burn_cap(burn_cap); 268 | coin::destroy_mint_cap(mint_cap); 269 | } 270 | 271 | #[test(aptos_framework = @0x1, account = @0xCAFE, test_one = @0x12, test_two = @0x34)] 272 | fun test_claim_birthday_gift_success( 273 | aptos_framework: &signer, 274 | account: &signer, 275 | test_one: &signer, 276 | test_two: &signer, 277 | ) { 278 | timestamp::set_time_has_started_for_testing(aptos_framework); 279 | let (burn_cap, mint_cap) = aptos_framework::aptos_coin::initialize_for_test(aptos_framework); 280 | 281 | let aptos_framework_address = signer::address_of(aptos_framework); 282 | let account_address = signer::address_of(account); 283 | let test_one_address = signer::address_of(test_one); 284 | let test_two_address = signer::address_of(test_two); 285 | 286 | account::create_account_for_test(aptos_framework_address); 287 | account::create_account_for_test(account_address); 288 | account::create_account_for_test(test_one_address); 289 | 290 | coin::register(account); 291 | coin::register(test_one); 292 | aptos_coin::mint(aptos_framework, account_address, 3000000); 293 | 294 | 295 | let addresses: vector
= vector::empty(); 296 | let amounts: vector = vector::empty(); 297 | let birthday_timestamps: vector = vector::empty(); 298 | 299 | vector::push_back(&mut addresses, test_one_address); 300 | vector::push_back(&mut addresses, test_two_address); 301 | 302 | vector::push_back(&mut amounts, 1000000); 303 | vector::push_back(&mut amounts, 2000000); 304 | 305 | vector::push_back(&mut birthday_timestamps, timestamp::now_seconds()); 306 | vector::push_back(&mut birthday_timestamps, timestamp::now_seconds()); 307 | 308 | initialize_distribution( 309 | account, 310 | addresses, 311 | amounts, 312 | birthday_timestamps 313 | ); 314 | 315 | timestamp::fast_forward_seconds(1); 316 | 317 | claim_birthday_gift(test_one, account_address); 318 | 319 | assert!(coin::balance(test_one_address) == 1000000, 0); 320 | 321 | coin::destroy_burn_cap(burn_cap); 322 | coin::destroy_mint_cap(mint_cap); 323 | } 324 | } --------------------------------------------------------------------------------