├── .gitattributes ├── .gitignore ├── README.md ├── TODO.md ├── aptos ├── Move.toml └── sources │ ├── acl.move │ ├── bcd.move │ ├── bloom_filter.move │ ├── box.move │ ├── crit_bit.move │ ├── date.move │ ├── governance.move │ ├── i128.move │ ├── i64.move │ ├── linear_vesting.move │ ├── math.move │ ├── math_safe_precise.move │ ├── math_u128.move │ ├── merkle_proof.move │ ├── pseudorandom.move │ ├── quadratic_vesting.move │ ├── to_string.move │ ├── u256.move │ ├── vectors.move │ └── virtual_block.move ├── package.json └── sui ├── Move.toml └── sources ├── acl.move ├── bcd.move ├── bloom_filter.move ├── box.move ├── crit_bit.move ├── date.move ├── escrow.move ├── escrow_shared.move ├── governance.move ├── i128.move ├── i64.move ├── linear_vesting.move ├── math.move ├── math_safe_precise.move ├── math_u128.move ├── merkle_proof.move ├── pseudorandom.move ├── quadratic_vesting.move ├── to_string.move ├── u256.move ├── vectors.move └── virtual_block.move /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Movemate 2 | 3 | **A library of module building blocks for Move on Aptos and Sui.** 4 | 5 | Movemate provides an advanced standard library of common modules in the Move ecosystem (working in tandem with the native frameworks), focusing on security, efficiency, composability, and ease of implementation. 6 | 7 | ## Modules 8 | 9 | * `acl`: Multi-role access control list (ACL). 10 | * `bcd`: Binary canonical DEserialization. Convert `vector` to `u64` and `u128`. 11 | * `bloom_filter`: Space-efficient probabilistic data structure for checking if an element is part of a set. 12 | * `box`: On Aptos, send objects without the recipient having to set up a store for themselves beforehand. On Sui, transfer objects with the `store` ability but not the `key` ability. 13 | * `crit_bit`: [Crit-bit trees](https://cr.yp.to/critbit.html) data structure. (Thanks to Econia.) 14 | * `date`: Date conversion library in Move. 15 | * `escrow` (Sui only): Very basic object escrow module on Sui. 16 | * `escrow_shared` (Sui only): Basic object escrow module with refunds and arbitration on Sui. 17 | * `governance`: On-chain coinholder governance (store coins and other objects; on Aptos, retrieve a `signer` for special actions like upgrading packages). 18 | * `i64`: Signed 64-bit integers. 19 | * `i128`: Signed 128-bit integers. 20 | * `linear_vesting`: Linear vesting of coins for a given beneficiary. 21 | * `math`: Standard math utilities missing in the Move language (for `u64`). 22 | * `math_safe_precise`: `mul_div` for `u64`s while avoiding overflow and a more precise `quadratic` function. 23 | * `math_u128`: Standard math utilities missing in the Move language (for `u128`). 24 | * `merkle_proof`: Merkle proof verification utilities. 25 | * `pseudorandom`: Pseudorandom number generator. 26 | * `to_string`: `u128` to `String` conversion utilities. 27 | * `u256`: Unsigned 256-bit integer implementation in Move. (Thanks to Pontem.) Includes bitwise operations and `vector` conversion. 28 | * `vectors`: Vector utilities--specifically, comparison operators and a binary search function. 29 | * `virtual_block`: Replace latency auctions with gas auctions (with control over MEV rewards) via virtual blocks. 30 | 31 | ### Outside the repo 32 | 33 | * [`multisig_wallet`: Multisignature wallet for coins and arbitrary objects.](https://github.com/pentagonxyz/multisig-wallet-move) 34 | * [`oracle_factory` (Sui only): Create and share custom oracles, aggregating data across validator sets.](https://github.com/pentagonxyz/move-oracles) 35 | * [`xyk_amm`: Constant product (XY=K) AMM (like Uniswap V2).](https://github.com/pentagonxyz/xyk-amm-move) 36 | 37 | ## Usage 38 | 39 | ### Sui 40 | 41 | Add the following to your `Move.toml`: 42 | 43 | ``` 44 | [dependencies.Movemate] 45 | git = "https://github.com/pentagonxyz/movemate.git" 46 | subdir = "sui" 47 | rev = "devnet" 48 | ``` 49 | 50 | ### Aptos 51 | 52 | Add the following to your `Move.toml`: 53 | 54 | ``` 55 | [dependencies.Movemate] 56 | git = "https://github.com/pentagonxyz/movemate.git" 57 | subdir = "aptos" 58 | rev = "devnet" 59 | ``` 60 | 61 | ## Testing 62 | 63 | ### Sui 64 | 65 | ``` 66 | cd movemate/sui 67 | sui move test --instructions 100000 68 | ``` 69 | 70 | ### Aptos 71 | 72 | ``` 73 | cd movemate/aptos 74 | aptos move test 75 | ``` 76 | 77 | ## Publishing 78 | 79 | ### Sui 80 | 81 | ``` 82 | sui client publish --path ./movemate/sui --gas-budget 30000 83 | ``` 84 | 85 | ### Aptos 86 | 87 | ``` 88 | aptos move publish --package-dir ./movemate/aptos 89 | aptos move run --function-id 0x3953993C1D8DFB8BAC2DA2F4DBA6521BA3E705299760FBEE6695E38BCE712A82::pseudorandom::init 90 | ``` 91 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | Ideas and improvements. 4 | 5 | ## Ideas 6 | 7 | * Finance/economics library. 8 | * Better fixed-point library. 9 | * Ed25519 signature utils in Sui. 10 | * Implement signatures in Sui [like (Aptos)](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/signature.move). 11 | * Data structures like double-ended queues, bitmaps, and (enumerable) sets? See [OpenZeppelin](https://docs.openzeppelin.com/contracts/4.x/api/utils#DoubleEndedQueue). 12 | * Cross-chain messaging. See [Starcoin's `EthStateVerifier`](https://github.com/starcoinorg/starcoin-framework-commons/blob/main/sources/EthStateVerifier.move). 13 | * `math_i64` and `math_i128` library. 14 | * `math_u256` library. 15 | * `i256` library. 16 | * Base 64 conversion library? 17 | 18 | ## Improvements 19 | 20 | * `i64` and `i128`: finish tests. 21 | * `math` and `math_u128`: add `abs` function, among others. 22 | * `quadratic_vesting`: merge with `linear_vesting`? 23 | * `quadratic_vesting`: better tests. 24 | * `governance`: finish tests. 25 | * `merkle_proof`: unit tests for multi-proof verification. 26 | * `governance`: make delegation optional? 27 | * `vector`, `merkle_proof`, etc.: fuzz testing? 28 | * `to_string`: support `u64`? 29 | * `to_string`: support `U256`? 30 | -------------------------------------------------------------------------------- /aptos/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "Movemate" 3 | version = "1.0.0" 4 | authors = ["David Lucid "] 5 | 6 | [addresses] 7 | std = "0x1" 8 | aptos_std = "0x1" 9 | aptos_framework = "0x1" 10 | movemate = "0x3953993C1D8DFB8BAC2DA2F4DBA6521BA3E705299760FBEE6695E38BCE712A82" 11 | 12 | [dependencies.MoveStdlib] 13 | git = "https://github.com/aptos-labs/aptos-core.git" 14 | subdir = "aptos-move/framework/move-stdlib" 15 | rev = "aptos-framework-v0.3.0" 16 | 17 | [dependencies.AptosStdlib] 18 | git = "https://github.com/aptos-labs/aptos-core.git" 19 | subdir = "aptos-move/framework/aptos-stdlib" 20 | rev = "aptos-framework-v0.3.0" 21 | 22 | [dependencies.AptosFramework] 23 | git = "https://github.com/aptos-labs/aptos-core.git" 24 | subdir = "aptos-move/framework/aptos-framework" 25 | rev = "aptos-framework-v0.3.0" 26 | -------------------------------------------------------------------------------- /aptos/sources/acl.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | /// @title acl 4 | /// @notice Multi-role access control list (ACL). 5 | /// @dev Maps addresses to `u128`s with each bit representing the presence of (or lack of) each role. 6 | module movemate::acl { 7 | use std::error; 8 | 9 | use aptos_framework::table::{Self, Table}; 10 | 11 | /// @dev When attempting to add/remove a role >= 128. 12 | const EROLE_NUMBER_TOO_LARGE: u64 = 0; 13 | 14 | /// @dev Maps addresses to `u128`s with each bit representing the presence of (or lack of) each role. 15 | struct ACL has store { 16 | permissions: Table 17 | } 18 | 19 | /// @notice Create a new ACL (access control list). 20 | public fun new(): ACL { 21 | ACL { permissions: table::new() } 22 | } 23 | 24 | /// @notice Check if a member has a role in the ACL. 25 | public fun has_role(acl: &ACL, member: address, role: u8): bool { 26 | assert!(role < 128, error::invalid_argument(EROLE_NUMBER_TOO_LARGE)); 27 | table::contains(&acl.permissions, member) && *table::borrow(&acl.permissions, member) & (1 << role) > 0 28 | } 29 | 30 | /// @notice Set all roles for a member in the ACL. 31 | /// @param permissions Permissions for a member, represented as a `u128` with each bit representing the presence of (or lack of) each role. 32 | public fun set_roles(acl: &mut ACL, member: address, permissions: u128) { 33 | if (table::contains(&acl.permissions, member)) *table::borrow_mut(&mut acl.permissions, member) = permissions 34 | else table::add(&mut acl.permissions, member, permissions); 35 | } 36 | 37 | /// @notice Add a role for a member in the ACL. 38 | public fun add_role(acl: &mut ACL, member: address, role: u8) { 39 | assert!(role < 128, error::invalid_argument(EROLE_NUMBER_TOO_LARGE)); 40 | if (table::contains(&acl.permissions, member)) { 41 | let perms = table::borrow_mut(&mut acl.permissions, member); 42 | *perms = *perms | (1 << role); 43 | } else { 44 | table::add(&mut acl.permissions, member, 1 << role); 45 | } 46 | } 47 | 48 | /// @notice Revoke a role for a member in the ACL. 49 | public fun remove_role(acl: &mut ACL, member: address, role: u8) { 50 | assert!(role < 128, error::invalid_argument(EROLE_NUMBER_TOO_LARGE)); 51 | if (table::contains(&acl.permissions, member)) { 52 | let perms = table::borrow_mut(&mut acl.permissions, member); 53 | *perms = *perms - (1 << role); 54 | } 55 | } 56 | 57 | #[test_only] 58 | struct TestACL has key { 59 | acl: ACL 60 | } 61 | 62 | #[test(dummy = @0x1234)] 63 | fun test_end_to_end(dummy: signer) { 64 | let acl = new(); 65 | add_role(&mut acl, @0x1234, 12); 66 | add_role(&mut acl, @0x1234, 99); 67 | add_role(&mut acl, @0x1234, 88); 68 | add_role(&mut acl, @0x1234, 123); 69 | add_role(&mut acl, @0x1234, 2); 70 | add_role(&mut acl, @0x1234, 1); 71 | remove_role(&mut acl, @0x1234, 2); 72 | set_roles(&mut acl, @0x5678, (1 << 123) | (1 << 2) | (1 << 1)); 73 | let i = 0; 74 | while (i < 128) { 75 | let has = has_role(&acl, @0x1234, i); 76 | assert!(if (i == 12 || i == 99 || i == 88 || i == 123 || i == 1) has else !has, 0); 77 | has = has_role(&acl, @0x5678, i); 78 | assert!(if (i == 123 || i == 2 || i == 1) has else !has, 1); 79 | i = i + 1; 80 | }; 81 | 82 | // can't drop so must store 83 | move_to(&dummy, TestACL { acl }); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /aptos/sources/bcd.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Source: https://github.com/starcoinorg/starcoin-framework-commons/blob/main/sources/PseudoRandom.move 3 | 4 | /// @title bcd 5 | /// @notice BCD = Binary canoncial DEserialization. 6 | module movemate::bcd { 7 | use std::vector; 8 | 9 | public fun bytes_to_u128(bytes: vector): u128 { 10 | let value = 0u128; 11 | let i = 0u64; 12 | while (i < 16) { 13 | value = value | ((*vector::borrow(&bytes, i) as u128) << ((8 * (15 - i)) as u8)); 14 | i = i + 1; 15 | }; 16 | return value 17 | } 18 | 19 | public fun bytes_to_u64(bytes: vector): u64 { 20 | let value = 0u64; 21 | let i = 0u64; 22 | while (i < 8) { 23 | value = value | ((*vector::borrow(&bytes, i) as u64) << ((8 * (7 - i)) as u8)); 24 | i = i + 1; 25 | }; 26 | return value 27 | } 28 | 29 | #[test] 30 | fun test_bytes_to_u64() { 31 | // binary: 01010001 11010011 10101111 11001100 11111101 00001001 10001110 11001101 32 | // bytes = [81, 211, 175, 204, 253, 9, 142, 205]; 33 | let dec = 5896249632111562445; 34 | 35 | let bytes = vector::empty(); 36 | vector::push_back(&mut bytes, 81); 37 | vector::push_back(&mut bytes, 211); 38 | vector::push_back(&mut bytes, 175); 39 | vector::push_back(&mut bytes, 204); 40 | vector::push_back(&mut bytes, 253); 41 | vector::push_back(&mut bytes, 9); 42 | vector::push_back(&mut bytes, 142); 43 | vector::push_back(&mut bytes, 205); 44 | 45 | let value = bytes_to_u64(bytes); 46 | assert!(value == dec, 101); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /aptos/sources/bloom_filter.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Based on: https://github.com/wanseob/solidity-bloom-filter/blob/master/contracts/BloomFilter.sol 3 | 4 | /// @title bloom_filter 5 | /// @dev Probabilistic data structure for checking if an element is part of a set. 6 | module movemate::bloom_filter { 7 | use std::error; 8 | use std::hash; 9 | use std::vector; 10 | 11 | use movemate::u256::{Self, U256}; 12 | 13 | const EHASH_COUNT_IS_ZERO: u64 = 0; 14 | const EVECTOR_LENGTH_NOT_32: u64 = 1; 15 | 16 | struct Filter has copy, drop, store { 17 | bitmap: U256, 18 | hash_count: u8 19 | } 20 | 21 | /// @dev It returns how many times it should be hashed, when the expected number of input items is `_item_num`. 22 | /// @param _item_num Expected number of input items 23 | public fun get_hash_count(_item_num: u64): u8 { 24 | let num_of_hash = (256 * 144) / (_item_num * 100) + 1; 25 | if (num_of_hash < 256) (num_of_hash as u8) else 255 26 | } 27 | 28 | /// @dev It returns updated bitmap when a new item is added into the bitmap 29 | /// @param _bitmap Original bitmap 30 | /// @param _hash_count How many times to hash. You should use the same value with the one which is used for the original bitmap. 31 | /// @param _item Hash value of an item 32 | public fun add_to_bitmap(_bitmap: U256, _hash_count: u8, _item: vector): U256 { 33 | assert!(_hash_count > 0, error::invalid_argument(EHASH_COUNT_IS_ZERO)); 34 | assert!(vector::length(&_item) == 32, error::invalid_argument(EVECTOR_LENGTH_NOT_32)); 35 | let i: u8 = 0; 36 | vector::push_back(&mut _item, 0); 37 | while (i < _hash_count) { 38 | *vector::borrow_mut(&mut _item, 32) = i; 39 | let position = vector::pop_back(&mut hash::sha2_256(_item)); 40 | let digest = u256::shl(u256::from_u128(1), position); 41 | _bitmap = u256::or(&_bitmap, &digest); 42 | i = i + 1; 43 | }; 44 | _bitmap 45 | } 46 | 47 | /// @dev It returns it may exist or definitely not exist. 48 | /// @param _bitmap Original bitmap 49 | /// @param _hash_count How many times to hash. You should use the same value with the one which is used for the original bitmap. 50 | /// @param _item Hash value of an item 51 | public fun false_positive(_bitmap: U256, _hash_count: u8, _item: vector): bool { 52 | assert!(_hash_count > 0, error::invalid_argument(EHASH_COUNT_IS_ZERO)); 53 | assert!(vector::length(&_item) == 32, error::invalid_argument(EVECTOR_LENGTH_NOT_32)); 54 | let i: u8 = 0; 55 | vector::push_back(&mut _item, 0); 56 | while (i < _hash_count) { 57 | *vector::borrow_mut(&mut _item, 32) = i; 58 | let position = vector::pop_back(&mut hash::sha2_256(_item)); 59 | let digest = u256::shl(u256::from_u128(1), position); 60 | if (_bitmap != u256::or(&_bitmap, &digest)) return false; 61 | i = i + 1; 62 | }; 63 | true 64 | } 65 | 66 | /// @dev It initialize the Filter struct. It sets the appropriate hash count for the expected number of item 67 | /// @param _itemNum Expected number of items to be added 68 | public fun new(_item_num: u64): Filter { 69 | Filter { 70 | bitmap: u256::zero(), 71 | hash_count: get_hash_count(_item_num) 72 | } 73 | } 74 | 75 | /// @dev It updates the bitmap of the filter using the given item value 76 | /// @param _item Hash value of an item 77 | public fun add(_filter: &mut Filter, _item: vector) { 78 | *&mut _filter.bitmap = add_to_bitmap(_filter.bitmap, _filter.hash_count, _item); 79 | } 80 | 81 | /// @dev It returns the filter may include the item or definitely now include it. 82 | /// @param _item Hash value of an item 83 | public fun check(_filter: &Filter, _item: vector): bool { 84 | false_positive(_filter.bitmap, _filter.hash_count, _item) 85 | } 86 | 87 | #[test] 88 | public fun test_end_to_end() { 89 | // Test init: check hash count 90 | let filter = new(10); 91 | assert!(filter.hash_count == 37, 0); // Hash count should equal 37 92 | 93 | // Test adding elements 94 | add(&mut filter, b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); 95 | let bitmap_a = filter.bitmap; 96 | add(&mut filter, b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); 97 | let bitmap_b = filter.bitmap; 98 | assert!(bitmap_b == bitmap_a, 1); // Adding same item should not update the bitmap 99 | add(&mut filter, b"cccccccccccccccccccccccccccccccc"); 100 | let bitmap_c = filter.bitmap; 101 | assert!(bitmap_c != bitmap_b, 2); // Adding different item should update the bitmap 102 | 103 | // Test checking for inclusion 104 | let included = b"abcdefghij"; 105 | let not_included = b"klmnopqrst"; 106 | let i = 0; 107 | while (i < 10) { 108 | let key = hash::sha2_256(vector::singleton(*vector::borrow(&included, i))); 109 | add(&mut filter, key); 110 | i = i + 1; 111 | }; 112 | let j = 0; 113 | while (j < 10) { 114 | let key = hash::sha2_256(vector::singleton(*vector::borrow(&included, j))); 115 | let false_positive = check(&filter, key); 116 | // It may exist or not 117 | assert!(false_positive, 3); // Should return false positive 118 | j = j + 1; 119 | }; 120 | let k = 0; 121 | while (k < 10) { 122 | let key = hash::sha2_256(vector::singleton(*vector::borrow(¬_included, k))); 123 | let false_positive = check(&filter, key); 124 | // It definitely does not exist 125 | assert!(!false_positive, 4); // Should return definitely not exist 126 | k = k + 1; 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /aptos/sources/box.move: -------------------------------------------------------------------------------- 1 | /// @title box 2 | /// @notice Generalized box for transferring objects to arbitrary recipients. 3 | module movemate::box { 4 | use std::error; 5 | use std::signer; 6 | use std::vector; 7 | 8 | use aptos_framework::iterable_table::{Self, IterableTable}; 9 | 10 | /// @dev When trying to call `box` before calling `init`. 11 | const ESENDER_BOXES_NOT_INITIALIZED: u64 = 0; 12 | 13 | struct Boxes has key, store { 14 | boxes: IterableTable> 15 | } 16 | 17 | struct PrivateBoxes has key, store { 18 | boxes: IterableTable> 19 | } 20 | 21 | /// @dev Enables boxing of type `T` under `sender`. 22 | public entry fun init(sender: &signer) { 23 | move_to(sender, Boxes { 24 | boxes: iterable_table::new() 25 | }); 26 | } 27 | 28 | /// @dev Stores the sent object in an box object. 29 | /// @param recipient The destination address of the box object. 30 | public entry fun box(sender: address, recipient: address, obj_in: T) acquires Boxes { 31 | assert!(exists>(sender), error::not_found(ESENDER_BOXES_NOT_INITIALIZED)); 32 | let boxes = &mut borrow_global_mut>(sender).boxes; 33 | let box = iterable_table::borrow_mut(boxes, recipient); 34 | vector::push_back(box, obj_in); 35 | } 36 | 37 | /// @dev Unboxes the object inside the box. 38 | public entry fun unbox(sender: address, recipient: &signer, index: u64): T acquires Boxes { 39 | let boxes = &mut borrow_global_mut>(sender).boxes; 40 | let box = iterable_table::borrow_mut(boxes, signer::address_of(recipient)); 41 | vector::swap_remove(box, index) 42 | } 43 | 44 | /// @dev Stores the sent object in an private box object. (Private box = verified senders only.) 45 | /// @param recipient The destination address of the box object. 46 | public entry fun box_private(sender: &signer, recipient: address, obj_in: T) acquires PrivateBoxes { 47 | let sender_address = signer::address_of(sender); 48 | if (exists>(sender_address)) { 49 | let boxes = &mut borrow_global_mut>(sender_address).boxes; 50 | let box = iterable_table::borrow_mut(boxes, recipient); 51 | vector::push_back(box, obj_in); 52 | } else { 53 | let boxes = iterable_table::new>(); 54 | iterable_table::add(&mut boxes, recipient, vector::singleton(obj_in)); 55 | move_to(sender, Boxes { 56 | boxes 57 | }); 58 | } 59 | } 60 | 61 | /// @dev Unboxes the object inside the private box. (Private box = verified senders only.) 62 | public entry fun unbox_private(sender: address, recipient: &signer, index: u64): T acquires PrivateBoxes { 63 | let boxes = &mut borrow_global_mut>(sender).boxes; 64 | let box = iterable_table::borrow_mut(boxes, signer::address_of(recipient)); 65 | vector::swap_remove(box, index) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /aptos/sources/date.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Based on: https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary 3 | 4 | /// @title date 5 | /// @dev Tested date range 1970/01/01 to 2345/12/31 6 | /// Conventions: 7 | /// Unit | Range | Notes 8 | /// :-------- |:-------------:|:----- 9 | /// timestamp | >= 0 | Unix timestamp, number of seconds since 1970/01/01 00:00:00 UTC 10 | /// year | 1970 ... 2345 | 11 | /// month | 1 ... 12 | 12 | /// day | 1 ... 31 | 13 | /// hour | 0 ... 23 | 14 | /// minute | 0 ... 59 | 15 | /// second | 0 ... 59 | 16 | /// dayOfWeek | 1 ... 7 | 1 = Monday, ..., 7 = Sunday 17 | module movemate::date { 18 | use std::error; 19 | 20 | const SECONDS_PER_DAY: u64 = 24 * 60 * 60; 21 | const SECONDS_PER_HOUR: u64 = 60 * 60; 22 | const SECONDS_PER_MINUTE: u64 = 60; 23 | const OFFSET19700101: u64 = 2440588; 24 | 25 | const DOW_MON: u64 = 1; 26 | const DOW_TUE: u64 = 2; 27 | const DOW_WED: u64 = 3; 28 | const DOW_THU: u64 = 4; 29 | const DOW_FRI: u64 = 5; 30 | const DOW_SAT: u64 = 6; 31 | const DOW_SUN: u64 = 7; 32 | 33 | const EYEAR_BEFORE_1970: u64 = 0; 34 | const EADDITION_ASSERTION_FAILED: u64 = 1; 35 | const ESUBTRACTION_ASSERTION_FAILED: u64 = 2; 36 | const EFROM_TIMESTAMP_LATER_THAN_TO_TIMESTAMP: u64 = 3; 37 | 38 | // ------------------------------------------------------------------------ 39 | // Calculate the number of days from 1970/01/01 to year/month/day using 40 | // the date conversion algorithm from 41 | // https://aa.usno.navy.mil/faq/JD_formula.html 42 | // and subtracting the offset 2440588 so that 1970/01/01 is day 0 43 | // 44 | // days = day 45 | // - 32075 46 | // + 1461 * (year + 4800 + (month - 14) / 12) / 4 47 | // + 367 * (month - 2 - (month - 14) / 12 * 12) / 12 48 | // - 3 * ((year + 4900 + (month - 14) / 12) / 100) / 4 49 | // - offset 50 | // ------------------------------------------------------------------------ 51 | public fun days_from_date(_year: u64, _month: u64, _day: u64): u64 { 52 | assert!(_year >= 1970, error::invalid_argument(EYEAR_BEFORE_1970)); 53 | let monthMinus14DividedBy12TimesNegative1 = if (_month < 3) 1 else 0; 54 | let __days = _day 55 | + 1461 * (_year + 4800 - monthMinus14DividedBy12TimesNegative1) / 4; 56 | let mm14db12tn1Times12PlusMonth = monthMinus14DividedBy12TimesNegative1 * 12 + _month; 57 | __days = if (mm14db12tn1Times12PlusMonth >= 2) __days + 367 * (mm14db12tn1Times12PlusMonth - 2) / 12 else __days - 367 * (2 - mm14db12tn1Times12PlusMonth) / 12; 58 | __days = __days - 3 * ((_year + 4900 - monthMinus14DividedBy12TimesNegative1) / 100) / 4 59 | - 32075 60 | - OFFSET19700101; 61 | __days 62 | } 63 | 64 | // ------------------------------------------------------------------------ 65 | // Calculate year/month/day from the number of days since 1970/01/01 using 66 | // the date conversion algorithm from 67 | // http://aa.usno.navy.mil/faq/docs/JD_Formula.php 68 | // and adding the offset 2440588 so that 1970/01/01 is day 0 69 | // 70 | // int L = days + 68569 + offset 71 | // int N = 4 * L / 146097 72 | // L = L - (146097 * N + 3) / 4 73 | // year = 4000 * (L + 1) / 1461001 74 | // L = L - 1461 * year / 4 + 31 75 | // month = 80 * L / 2447 76 | // dd = L - 2447 * month / 80 77 | // L = month / 11 78 | // month = month + 2 - 12 * L 79 | // year = 100 * (N - 49) + year + L 80 | // ------------------------------------------------------------------------ 81 | public fun days_to_date(days: u64): (u64, u64, u64) { 82 | let l = days + 68569 + OFFSET19700101; 83 | let n = 4 * l / 146097; 84 | l = l - (146097 * n + 3) / 4; 85 | let _year = 4000 * (l + 1) / 1461001; 86 | l = l - 1461 * _year / 4 + 31; 87 | let _month = 80 * l / 2447; 88 | let _day = l - 2447 * _month / 80; 89 | l = _month / 11; 90 | _month = _month + 2 - 12 * l; 91 | _year = 100 * (n - 49) + _year + l; 92 | 93 | (_year, _month, _day) 94 | } 95 | 96 | public fun timestamp_from_date(year: u64, month: u64, day: u64): u64 { 97 | days_from_date(year, month, day) * SECONDS_PER_DAY 98 | } 99 | 100 | public fun timestamp_from_date_time(year: u64, month: u64, day: u64, hour: u64, minute: u64, second: u64): u64 { 101 | days_from_date(year, month, day) * SECONDS_PER_DAY + hour * SECONDS_PER_HOUR + minute * SECONDS_PER_MINUTE + second 102 | } 103 | 104 | public fun timestamp_to_date(timestamp: u64): (u64, u64, u64) { 105 | days_to_date(timestamp / SECONDS_PER_DAY) 106 | } 107 | 108 | public fun timestamp_to_date_time(timestamp: u64): (u64, u64, u64, u64, u64, u64) { 109 | let (year, month, day) = days_to_date(timestamp / SECONDS_PER_DAY); 110 | let secs = timestamp % SECONDS_PER_DAY; 111 | let hour = secs / SECONDS_PER_HOUR; 112 | secs = secs % SECONDS_PER_HOUR; 113 | let minute = secs / SECONDS_PER_MINUTE; 114 | let second = secs % SECONDS_PER_MINUTE; 115 | (year, month, day, hour, minute, second) 116 | } 117 | 118 | public fun is_valid_date(year: u64, month: u64, day: u64): bool { 119 | if (year >= 1970 && month > 0 && month <= 12) { 120 | let days_in_month = get_days_in_year_month(year, month); 121 | if (day > 0 && day <= days_in_month) return true; 122 | }; 123 | false 124 | } 125 | 126 | public fun is_valid_date_time(year: u64, month: u64, day: u64, hour: u64, minute: u64, second: u64): bool { 127 | is_valid_date(year, month, day) && hour < 24 && minute < 60 && second < 60 128 | } 129 | 130 | public fun is_timestamp_leap_year(timestamp: u64): bool { 131 | let (year, _, _) = days_to_date(timestamp / SECONDS_PER_DAY); 132 | is_year_leap_year(year) 133 | } 134 | 135 | public fun is_year_leap_year(year: u64): bool { 136 | ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0) 137 | } 138 | 139 | public fun is_weekday(timestamp: u64): bool { 140 | get_day_of_week(timestamp) <= DOW_FRI 141 | } 142 | 143 | public fun is_weekend(timestamp: u64): bool { 144 | get_day_of_week(timestamp) >= DOW_SAT 145 | } 146 | 147 | public fun get_days_in_timestamp_month(timestamp: u64): u64 { 148 | let (year, month, _) = days_to_date(timestamp / SECONDS_PER_DAY); 149 | get_days_in_year_month(year, month) 150 | } 151 | 152 | public fun get_days_in_year_month(year: u64, month: u64): u64 { 153 | if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) return 31; 154 | if (month != 2) return 30; 155 | if (is_year_leap_year(year)) 29 else 28 156 | } 157 | 158 | // 1 = Monday, 7 = Sunday 159 | public fun get_day_of_week(timestamp: u64): u64 { 160 | let _days = timestamp / SECONDS_PER_DAY; 161 | (_days + 3) % 7 + 1 162 | } 163 | 164 | public fun get_year(timestamp: u64): u64 { 165 | let (year, _, _) = days_to_date(timestamp / SECONDS_PER_DAY); 166 | year 167 | } 168 | 169 | public fun get_month(timestamp: u64): u64 { 170 | let (_, month, _) = days_to_date(timestamp / SECONDS_PER_DAY); 171 | month 172 | } 173 | 174 | public fun get_day(timestamp: u64): u64 { 175 | let (_, _, day) = days_to_date(timestamp / SECONDS_PER_DAY); 176 | day 177 | } 178 | 179 | public fun get_hour(timestamp: u64): u64 { 180 | let secs = timestamp % SECONDS_PER_DAY; 181 | secs / SECONDS_PER_HOUR 182 | } 183 | 184 | public fun get_minute(timestamp: u64): u64 { 185 | let secs = timestamp % SECONDS_PER_HOUR; 186 | secs / SECONDS_PER_MINUTE 187 | } 188 | 189 | public fun get_second(timestamp: u64): u64 { 190 | timestamp % SECONDS_PER_MINUTE 191 | } 192 | 193 | public fun add_years(timestamp: u64, _years: u64): u64 { 194 | let (year, month, day) = days_to_date(timestamp / SECONDS_PER_DAY); 195 | year = year + _years; 196 | let days_in_month = get_days_in_year_month(year, month); 197 | if (day > days_in_month) day = days_in_month; 198 | let new_timestamp = days_from_date(year, month, day) * SECONDS_PER_DAY + timestamp % SECONDS_PER_DAY; 199 | assert!(new_timestamp >= timestamp, error::internal(EADDITION_ASSERTION_FAILED)); 200 | new_timestamp 201 | } 202 | 203 | public fun add_months(timestamp: u64, _months: u64): u64 { 204 | let (year, month, day) = days_to_date(timestamp / SECONDS_PER_DAY); 205 | month = month + _months; 206 | year = year + (month - 1) / 12; 207 | month = (month - 1) % 12 + 1; 208 | let days_in_month = get_days_in_year_month(year, month); 209 | if (day > days_in_month) day = days_in_month; 210 | let new_timestamp = days_from_date(year, month, day) * SECONDS_PER_DAY + timestamp % SECONDS_PER_DAY; 211 | assert!(new_timestamp >= timestamp, error::internal(EADDITION_ASSERTION_FAILED)); 212 | new_timestamp 213 | } 214 | 215 | public fun add_days(timestamp: u64, _days: u64): u64 { 216 | timestamp + _days * SECONDS_PER_DAY 217 | } 218 | 219 | public fun add_hours(timestamp: u64, _hours: u64): u64 { 220 | timestamp + _hours * SECONDS_PER_HOUR 221 | } 222 | 223 | public fun add_minutes(timestamp: u64, _minutes: u64): u64 { 224 | timestamp + _minutes * SECONDS_PER_MINUTE 225 | } 226 | 227 | public fun add_seconds(timestamp: u64, _seconds: u64): u64 { 228 | timestamp + _seconds 229 | } 230 | 231 | public fun sub_years(timestamp: u64, _years: u64): u64 { 232 | let (year, month, day) = days_to_date(timestamp / SECONDS_PER_DAY); 233 | year = year - _years; 234 | let days_in_month = get_days_in_year_month(year, month); 235 | if (day > days_in_month) day = days_in_month; 236 | let new_timestamp = days_from_date(year, month, day) * SECONDS_PER_DAY + timestamp % SECONDS_PER_DAY; 237 | assert!(new_timestamp <= timestamp, error::internal(ESUBTRACTION_ASSERTION_FAILED)); 238 | new_timestamp 239 | } 240 | public fun sub_months(timestamp: u64, _months: u64): u64 { 241 | let (year, month, day) = days_to_date(timestamp / SECONDS_PER_DAY); 242 | let year_month = year * 12 + (month - 1) - _months; 243 | year = year_month / 12; 244 | month = year_month % 12 + 1; 245 | let days_in_month = get_days_in_year_month(year, month); 246 | if (day > days_in_month) day = days_in_month; 247 | let new_timestamp = days_from_date(year, month, day) * SECONDS_PER_DAY + timestamp % SECONDS_PER_DAY; 248 | assert!(new_timestamp <= timestamp, error::internal(ESUBTRACTION_ASSERTION_FAILED)); 249 | new_timestamp 250 | } 251 | 252 | public fun sub_days(timestamp: u64, _days: u64): u64 { 253 | timestamp - _days * SECONDS_PER_DAY 254 | } 255 | 256 | public fun sub_hours(timestamp: u64, _hours: u64): u64 { 257 | timestamp - _hours * SECONDS_PER_HOUR 258 | } 259 | 260 | public fun sub_minutes(timestamp: u64, _minutes: u64): u64 { 261 | timestamp - _minutes * SECONDS_PER_MINUTE 262 | } 263 | 264 | public fun sub_seconds(timestamp: u64, _seconds: u64): u64 { 265 | timestamp - _seconds 266 | } 267 | 268 | public fun diff_years(from_timestamp: u64, to_timestamp: u64): u64 { 269 | assert!(from_timestamp <= to_timestamp, error::invalid_argument(EFROM_TIMESTAMP_LATER_THAN_TO_TIMESTAMP)); 270 | let (from_year, _, _) = days_to_date(from_timestamp / SECONDS_PER_DAY); 271 | let (to_year, _, _) = days_to_date(to_timestamp / SECONDS_PER_DAY); 272 | to_year - from_year 273 | } 274 | 275 | public fun diff_months(from_timestamp: u64, to_timestamp: u64): u64 { 276 | assert!(from_timestamp <= to_timestamp, error::invalid_argument(EFROM_TIMESTAMP_LATER_THAN_TO_TIMESTAMP)); 277 | let (from_year, from_month, _) = days_to_date(from_timestamp / SECONDS_PER_DAY); 278 | let (to_year, to_month, _) = days_to_date(to_timestamp / SECONDS_PER_DAY); 279 | to_year * 12 + to_month - from_year * 12 - from_month 280 | } 281 | 282 | public fun diff_days(from_timestamp: u64, to_timestamp: u64): u64 { 283 | assert!(from_timestamp <= to_timestamp, error::invalid_argument(EFROM_TIMESTAMP_LATER_THAN_TO_TIMESTAMP)); 284 | (to_timestamp - from_timestamp) / SECONDS_PER_DAY 285 | } 286 | 287 | public fun diff_hours(from_timestamp: u64, to_timestamp: u64): u64 { 288 | assert!(from_timestamp <= to_timestamp, error::invalid_argument(EFROM_TIMESTAMP_LATER_THAN_TO_TIMESTAMP)); 289 | (to_timestamp - from_timestamp) / SECONDS_PER_HOUR 290 | } 291 | 292 | public fun diff_minutes(from_timestamp: u64, to_timestamp: u64): u64 { 293 | assert!(from_timestamp <= to_timestamp, error::invalid_argument(EFROM_TIMESTAMP_LATER_THAN_TO_TIMESTAMP)); 294 | (to_timestamp - from_timestamp) / SECONDS_PER_MINUTE 295 | } 296 | 297 | public fun diff_seconds(from_timestamp: u64, to_timestamp: u64): u64 { 298 | assert!(from_timestamp <= to_timestamp, error::invalid_argument(EFROM_TIMESTAMP_LATER_THAN_TO_TIMESTAMP)); 299 | to_timestamp - from_timestamp 300 | } 301 | 302 | #[test] 303 | public entry fun test_days_from_date_examples() { 304 | assert!(days_from_date(2019, 12, 20) == 18250, 0); 305 | } 306 | 307 | #[test] 308 | public entry fun test_days_to_date_examples() { 309 | let (y, m, d) = days_to_date(18250); 310 | assert!(m == 12, 0); 311 | assert!(d == 20, 1); 312 | assert!(y == 2019, 2); 313 | } 314 | 315 | #[test] 316 | public entry fun test_days_to_date_date_to_days() { 317 | let i = 0; 318 | while (i <= 200 * 365) { 319 | let (y, m, d) = days_to_date(i); 320 | assert!(days_from_date(y, m, d) == i, 0); 321 | i = i + 99; 322 | } 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /aptos/sources/i128.move: -------------------------------------------------------------------------------- 1 | /// @title i128 2 | /// @notice Signed 128-bit integers in Move. 3 | /// @dev TODO: Pass in params by value instead of by ref to make usage easier? 4 | module movemate::i128 { 5 | use std::error; 6 | 7 | /// @dev Maximum I128 value as a u128. 8 | const MAX_I128_AS_U128: u128 = (1 << 127) - 1; 9 | 10 | /// @dev u128 with the first bit set. An `I128` is negative if this bit is set. 11 | const U128_WITH_FIRST_BIT_SET: u128 = 1 << 127; 12 | 13 | /// When both `U256` equal. 14 | const EQUAL: u8 = 0; 15 | 16 | /// When `a` is less than `b`. 17 | const LESS_THAN: u8 = 1; 18 | 19 | /// When `b` is greater than `b`. 20 | const GREATER_THAN: u8 = 2; 21 | 22 | /// @dev When trying to convert from a u128 > MAX_I128_AS_U128 to an I128. 23 | const ECONVERSION_FROM_U128_OVERFLOW: u64 = 0; 24 | 25 | /// @dev When trying to convert from an negative I128 to a u128. 26 | const ECONVERSION_TO_U128_UNDERFLOW: u64 = 1; 27 | 28 | /// @notice Struct representing a signed 128-bit integer. 29 | struct I128 has copy, drop, store { 30 | bits: u128 31 | } 32 | 33 | /// @notice Casts a `u128` to an `I128`. 34 | public fun from(x: u128): I128 { 35 | assert!(x <= MAX_I128_AS_U128, error::invalid_argument(ECONVERSION_FROM_U128_OVERFLOW)); 36 | I128 { bits: x } 37 | } 38 | 39 | /// @notice Creates a new `I128` with value 0. 40 | public fun zero(): I128 { 41 | I128 { bits: 0 } 42 | } 43 | 44 | /// @notice Casts an `I128` to a `u128`. 45 | public fun as_u128(x: &I128): u128 { 46 | assert!(x.bits < U128_WITH_FIRST_BIT_SET, error::invalid_argument(ECONVERSION_TO_U128_UNDERFLOW)); 47 | x.bits 48 | } 49 | 50 | /// @notice Whether or not `x` is equal to 0. 51 | public fun is_zero(x: &I128): bool { 52 | x.bits == 0 53 | } 54 | 55 | /// @notice Whether or not `x` is negative. 56 | public fun is_neg(x: &I128): bool { 57 | x.bits > U128_WITH_FIRST_BIT_SET 58 | } 59 | 60 | /// @notice Flips the sign of `x`. 61 | public fun neg(x: &I128): I128 { 62 | if (x.bits == 0) return *x; 63 | I128 { bits: if (x.bits < U128_WITH_FIRST_BIT_SET) x.bits | (1 << 127) else x.bits - (1 << 127) } 64 | } 65 | 66 | /// @notice Flips the sign of `x`. 67 | public fun neg_from(x: u128): I128 { 68 | let ret = from(x); 69 | if (ret.bits > 0) *&mut ret.bits = ret.bits | (1 << 127); 70 | ret 71 | } 72 | 73 | /// @notice Absolute value of `x`. 74 | public fun abs(x: &I128): I128 { 75 | if (x.bits < U128_WITH_FIRST_BIT_SET) *x else I128 { bits: x.bits - (1 << 127) } 76 | } 77 | 78 | /// @notice Compare `a` and `b`. 79 | public fun compare(a: &I128, b: &I128): u8 { 80 | if (a.bits == b.bits) return EQUAL; 81 | if (a.bits < U128_WITH_FIRST_BIT_SET) { 82 | // A is positive 83 | if (b.bits < U128_WITH_FIRST_BIT_SET) { 84 | // B is positive 85 | return if (a.bits > b.bits) GREATER_THAN else LESS_THAN 86 | } else { 87 | // B is negative 88 | return GREATER_THAN 89 | } 90 | } else { 91 | // A is negative 92 | if (b.bits < U128_WITH_FIRST_BIT_SET) { 93 | // B is positive 94 | return LESS_THAN 95 | } else { 96 | // B is negative 97 | return if (a.bits > b.bits) LESS_THAN else GREATER_THAN 98 | } 99 | } 100 | } 101 | 102 | /// @notice Add `a + b`. 103 | public fun add(a: &I128, b: &I128): I128 { 104 | if (a.bits >> 127 == 0) { 105 | // A is positive 106 | if (b.bits >> 127 == 0) { 107 | // B is positive 108 | return I128 { bits: a.bits + b.bits } 109 | } else { 110 | // B is negative 111 | if (b.bits - (1 << 127) <= a.bits) return I128 { bits: a.bits - (b.bits - (1 << 127)) }; // Return positive 112 | return I128 { bits: b.bits - a.bits } // Return negative 113 | } 114 | } else { 115 | // A is negative 116 | if (b.bits >> 127 == 0) { 117 | // B is positive 118 | if (a.bits - (1 << 127) <= b.bits) return I128 { bits: b.bits - (a.bits - (1 << 127)) }; // Return positive 119 | return I128 { bits: a.bits - b.bits } // Return negative 120 | } else { 121 | // B is negative 122 | return I128 { bits: a.bits + (b.bits - (1 << 127)) } 123 | } 124 | } 125 | } 126 | 127 | /// @notice Subtract `a - b`. 128 | public fun sub(a: &I128, b: &I128): I128 { 129 | if (a.bits >> 127 == 0) { 130 | // A is positive 131 | if (b.bits >> 127 == 0) { 132 | // B is positive 133 | if (a.bits >= b.bits) return I128 { bits: a.bits - b.bits }; // Return positive 134 | return I128 { bits: (1 << 127) | (b.bits - a.bits) } // Return negative 135 | } else { 136 | // B is negative 137 | return I128 { bits: a.bits + (b.bits - (1 << 127)) } // Return negative 138 | } 139 | } else { 140 | // A is negative 141 | if (b.bits >> 127 == 0) { 142 | // B is positive 143 | return I128 { bits: a.bits + b.bits } // Return negative 144 | } else { 145 | // B is negative 146 | if (b.bits >= a.bits) return I128 { bits: b.bits - a.bits }; // Return positive 147 | return I128 { bits: a.bits - (b.bits - (1 << 127)) } // Return negative 148 | } 149 | } 150 | } 151 | 152 | /// @notice Multiply `a * b`. 153 | public fun mul(a: &I128, b: &I128): I128 { 154 | if (a.bits >> 127 == 0) { 155 | // A is positive 156 | if (b.bits >> 127 == 0) { 157 | // B is positive 158 | return I128 { bits: a.bits * b.bits } // Return positive 159 | } else { 160 | // B is negative 161 | return I128 { bits: (1 << 127) | (a.bits * (b.bits - (1 << 127))) } // Return negative 162 | } 163 | } else { 164 | // A is negative 165 | if (b.bits >> 127 == 0) { 166 | // B is positive 167 | return I128 { bits: (1 << 127) | (b.bits * (a.bits - (1 << 127))) } // Return negative 168 | } else { 169 | // B is negative 170 | return I128 { bits: (a.bits - (1 << 127)) * (b.bits - (1 << 127)) } // Return positive 171 | } 172 | } 173 | } 174 | 175 | /// @notice Divide `a / b`. 176 | public fun div(a: &I128, b: &I128): I128 { 177 | if (a.bits >> 127 == 0) { 178 | // A is positive 179 | if (b.bits >> 127 == 0) { 180 | // B is positive 181 | return I128 { bits: a.bits / b.bits } // Return positive 182 | } else { 183 | // B is negative 184 | return I128 { bits: (1 << 127) | (a.bits / (b.bits - (1 << 127))) } // Return negative 185 | } 186 | } else { 187 | // A is negative 188 | if (b.bits >> 127 == 0) { 189 | // B is positive 190 | return I128 { bits: (1 << 127) | ((a.bits - (1 << 127)) / b.bits) } // Return negative 191 | } else { 192 | // B is negative 193 | return I128 { bits: (a.bits - (1 << 127)) / (b.bits - (1 << 127)) } // Return positive 194 | } 195 | } 196 | } 197 | 198 | #[test] 199 | fun test_compare() { 200 | assert!(compare(&from(123), &from(123)) == EQUAL, 0); 201 | assert!(compare(&neg_from(123), &neg_from(123)) == EQUAL, 0); 202 | assert!(compare(&from(234), &from(123)) == GREATER_THAN, 0); 203 | assert!(compare(&from(123), &from(234)) == LESS_THAN, 0); 204 | assert!(compare(&neg_from(234), &neg_from(123)) == LESS_THAN, 0); 205 | assert!(compare(&neg_from(123), &neg_from(234)) == GREATER_THAN, 0); 206 | assert!(compare(&from(123), &neg_from(234)) == GREATER_THAN, 0); 207 | assert!(compare(&neg_from(123), &from(234)) == LESS_THAN, 0); 208 | assert!(compare(&from(234), &neg_from(123)) == GREATER_THAN, 0); 209 | assert!(compare(&neg_from(234), &from(123)) == LESS_THAN, 0); 210 | } 211 | 212 | #[test] 213 | fun test_add() { 214 | assert!(add(&from(123), &from(234)) == from(357), 0); 215 | assert!(add(&from(123), &neg_from(234)) == neg_from(111), 0); 216 | assert!(add(&from(234), &neg_from(123)) == from(111), 0); 217 | assert!(add(&neg_from(123), &from(234)) == from(111), 0); 218 | assert!(add(&neg_from(123), &neg_from(234)) == neg_from(357), 0); 219 | assert!(add(&neg_from(234), &neg_from(123)) == neg_from(357), 0); 220 | 221 | assert!(add(&from(123), &neg_from(123)) == zero(), 0); 222 | assert!(add(&neg_from(123), &from(123)) == zero(), 0); 223 | } 224 | 225 | #[test] 226 | fun test_sub() { 227 | assert!(sub(&from(123), &from(234)) == neg_from(111), 0); 228 | assert!(sub(&from(234), &from(123)) == from(111), 0); 229 | assert!(sub(&from(123), &neg_from(234)) == from(357), 0); 230 | assert!(sub(&neg_from(123), &from(234)) == neg_from(357), 0); 231 | assert!(sub(&neg_from(123), &neg_from(234)) == from(111), 0); 232 | assert!(sub(&neg_from(234), &neg_from(123)) == neg_from(111), 0); 233 | 234 | assert!(sub(&from(123), &from(123)) == zero(), 0); 235 | assert!(sub(&neg_from(123), &neg_from(123)) == zero(), 0); 236 | } 237 | 238 | #[test] 239 | fun test_mul() { 240 | assert!(mul(&from(123), &from(234)) == from(28782), 0); 241 | assert!(mul(&from(123), &neg_from(234)) == neg_from(28782), 0); 242 | assert!(mul(&neg_from(123), &from(234)) == neg_from(28782), 0); 243 | assert!(mul(&neg_from(123), &neg_from(234)) == from(28782), 0); 244 | } 245 | 246 | #[test] 247 | fun test_div() { 248 | assert!(div(&from(28781), &from(123)) == from(233), 0); 249 | assert!(div(&from(28781), &neg_from(123)) == neg_from(233), 0); 250 | assert!(div(&neg_from(28781), &from(123)) == neg_from(233), 0); 251 | assert!(div(&neg_from(28781), &neg_from(123)) == from(233), 0); 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /aptos/sources/i64.move: -------------------------------------------------------------------------------- 1 | /// @title i64 2 | /// @notice Signed 64-bit integers in Move. 3 | /// @dev TODO: Pass in params by value instead of by ref to make usage easier? 4 | module movemate::i64 { 5 | use std::error; 6 | 7 | /// @dev Maximum I64 value as a u64. 8 | const MAX_I64_AS_U64: u64 = (1 << 63) - 1; 9 | 10 | /// @dev u64 with the first bit set. An `I64` is negative if this bit is set. 11 | const U64_WITH_FIRST_BIT_SET: u64 = 1 << 63; 12 | 13 | /// When both `U256` equal. 14 | const EQUAL: u8 = 0; 15 | 16 | /// When `a` is less than `b`. 17 | const LESS_THAN: u8 = 1; 18 | 19 | /// When `b` is greater than `b`. 20 | const GREATER_THAN: u8 = 2; 21 | 22 | /// @dev When trying to convert from a u64 > MAX_I64_AS_U64 to an I64. 23 | const ECONVERSION_FROM_U64_OVERFLOW: u64 = 0; 24 | 25 | /// @dev When trying to convert from an negative I64 to a u64. 26 | const ECONVERSION_TO_U64_UNDERFLOW: u64 = 1; 27 | 28 | /// @notice Struct representing a signed 64-bit integer. 29 | struct I64 has copy, drop, store { 30 | bits: u64 31 | } 32 | 33 | /// @notice Casts a `u64` to an `I64`. 34 | public fun from(x: u64): I64 { 35 | assert!(x <= MAX_I64_AS_U64, error::invalid_argument(ECONVERSION_FROM_U64_OVERFLOW)); 36 | I64 { bits: x } 37 | } 38 | 39 | /// @notice Creates a new `I64` with value 0. 40 | public fun zero(): I64 { 41 | I64 { bits: 0 } 42 | } 43 | 44 | /// @notice Casts an `I64` to a `u64`. 45 | public fun as_u64(x: &I64): u64 { 46 | assert!(x.bits < U64_WITH_FIRST_BIT_SET, error::invalid_argument(ECONVERSION_TO_U64_UNDERFLOW)); 47 | x.bits 48 | } 49 | 50 | /// @notice Whether or not `x` is equal to 0. 51 | public fun is_zero(x: &I64): bool { 52 | x.bits == 0 53 | } 54 | 55 | /// @notice Whether or not `x` is negative. 56 | public fun is_neg(x: &I64): bool { 57 | x.bits > U64_WITH_FIRST_BIT_SET 58 | } 59 | 60 | /// @notice Flips the sign of `x`. 61 | public fun neg(x: &I64): I64 { 62 | if (x.bits == 0) return *x; 63 | I64 { bits: if (x.bits < U64_WITH_FIRST_BIT_SET) x.bits | (1 << 63) else x.bits - (1 << 63) } 64 | } 65 | 66 | /// @notice Flips the sign of `x`. 67 | public fun neg_from(x: u64): I64 { 68 | let ret = from(x); 69 | if (ret.bits > 0) *&mut ret.bits = ret.bits | (1 << 63); 70 | ret 71 | } 72 | 73 | /// @notice Absolute value of `x`. 74 | public fun abs(x: &I64): I64 { 75 | if (x.bits < U64_WITH_FIRST_BIT_SET) *x else I64 { bits: x.bits - (1 << 63) } 76 | } 77 | 78 | /// @notice Compare `a` and `b`. 79 | public fun compare(a: &I64, b: &I64): u8 { 80 | if (a.bits == b.bits) return EQUAL; 81 | if (a.bits < U64_WITH_FIRST_BIT_SET) { 82 | // A is positive 83 | if (b.bits < U64_WITH_FIRST_BIT_SET) { 84 | // B is positive 85 | return if (a.bits > b.bits) GREATER_THAN else LESS_THAN 86 | } else { 87 | // B is negative 88 | return GREATER_THAN 89 | } 90 | } else { 91 | // A is negative 92 | if (b.bits < U64_WITH_FIRST_BIT_SET) { 93 | // B is positive 94 | return LESS_THAN 95 | } else { 96 | // B is negative 97 | return if (a.bits > b.bits) LESS_THAN else GREATER_THAN 98 | } 99 | } 100 | } 101 | 102 | /// @notice Add `a + b`. 103 | public fun add(a: &I64, b: &I64): I64 { 104 | if (a.bits >> 63 == 0) { 105 | // A is positive 106 | if (b.bits >> 63 == 0) { 107 | // B is positive 108 | return I64 { bits: a.bits + b.bits } 109 | } else { 110 | // B is negative 111 | if (b.bits - (1 << 63) <= a.bits) return I64 { bits: a.bits - (b.bits - (1 << 63)) }; // Return positive 112 | return I64 { bits: b.bits - a.bits } // Return negative 113 | } 114 | } else { 115 | // A is negative 116 | if (b.bits >> 63 == 0) { 117 | // B is positive 118 | if (a.bits - (1 << 63) <= b.bits) return I64 { bits: b.bits - (a.bits - (1 << 63)) }; // Return positive 119 | return I64 { bits: a.bits - b.bits } // Return negative 120 | } else { 121 | // B is negative 122 | return I64 { bits: a.bits + (b.bits - (1 << 63)) } 123 | } 124 | } 125 | } 126 | 127 | /// @notice Subtract `a - b`. 128 | public fun sub(a: &I64, b: &I64): I64 { 129 | if (a.bits >> 63 == 0) { 130 | // A is positive 131 | if (b.bits >> 63 == 0) { 132 | // B is positive 133 | if (a.bits >= b.bits) return I64 { bits: a.bits - b.bits }; // Return positive 134 | return I64 { bits: (1 << 63) | (b.bits - a.bits) } // Return negative 135 | } else { 136 | // B is negative 137 | return I64 { bits: a.bits + (b.bits - (1 << 63)) } // Return negative 138 | } 139 | } else { 140 | // A is negative 141 | if (b.bits >> 63 == 0) { 142 | // B is positive 143 | return I64 { bits: a.bits + b.bits } // Return negative 144 | } else { 145 | // B is negative 146 | if (b.bits >= a.bits) return I64 { bits: b.bits - a.bits }; // Return positive 147 | return I64 { bits: a.bits - (b.bits - (1 << 63)) } // Return negative 148 | } 149 | } 150 | } 151 | 152 | /// @notice Multiply `a * b`. 153 | public fun mul(a: &I64, b: &I64): I64 { 154 | if (a.bits >> 63 == 0) { 155 | // A is positive 156 | if (b.bits >> 63 == 0) { 157 | // B is positive 158 | return I64 { bits: a.bits * b.bits } // Return positive 159 | } else { 160 | // B is negative 161 | return I64 { bits: (1 << 63) | (a.bits * (b.bits - (1 << 63))) } // Return negative 162 | } 163 | } else { 164 | // A is negative 165 | if (b.bits >> 63 == 0) { 166 | // B is positive 167 | return I64 { bits: (1 << 63) | (b.bits * (a.bits - (1 << 63))) } // Return negative 168 | } else { 169 | // B is negative 170 | return I64 { bits: (a.bits - (1 << 63)) * (b.bits - (1 << 63)) } // Return positive 171 | } 172 | } 173 | } 174 | 175 | /// @notice Divide `a / b`. 176 | public fun div(a: &I64, b: &I64): I64 { 177 | if (a.bits >> 63 == 0) { 178 | // A is positive 179 | if (b.bits >> 63 == 0) { 180 | // B is positive 181 | return I64 { bits: a.bits / b.bits } // Return positive 182 | } else { 183 | // B is negative 184 | return I64 { bits: (1 << 63) | (a.bits / (b.bits - (1 << 63))) } // Return negative 185 | } 186 | } else { 187 | // A is negative 188 | if (b.bits >> 63 == 0) { 189 | // B is positive 190 | return I64 { bits: (1 << 63) | ((a.bits - (1 << 63)) / b.bits) } // Return negative 191 | } else { 192 | // B is negative 193 | return I64 { bits: (a.bits - (1 << 63)) / (b.bits - (1 << 63)) } // Return positive 194 | } 195 | } 196 | } 197 | 198 | #[test] 199 | fun test_compare() { 200 | assert!(compare(&from(123), &from(123)) == EQUAL, 0); 201 | assert!(compare(&neg_from(123), &neg_from(123)) == EQUAL, 0); 202 | assert!(compare(&from(234), &from(123)) == GREATER_THAN, 0); 203 | assert!(compare(&from(123), &from(234)) == LESS_THAN, 0); 204 | assert!(compare(&neg_from(234), &neg_from(123)) == LESS_THAN, 0); 205 | assert!(compare(&neg_from(123), &neg_from(234)) == GREATER_THAN, 0); 206 | assert!(compare(&from(123), &neg_from(234)) == GREATER_THAN, 0); 207 | assert!(compare(&neg_from(123), &from(234)) == LESS_THAN, 0); 208 | assert!(compare(&from(234), &neg_from(123)) == GREATER_THAN, 0); 209 | assert!(compare(&neg_from(234), &from(123)) == LESS_THAN, 0); 210 | } 211 | 212 | #[test] 213 | fun test_add() { 214 | assert!(add(&from(123), &from(234)) == from(357), 0); 215 | assert!(add(&from(123), &neg_from(234)) == neg_from(111), 0); 216 | assert!(add(&from(234), &neg_from(123)) == from(111), 0); 217 | assert!(add(&neg_from(123), &from(234)) == from(111), 0); 218 | assert!(add(&neg_from(123), &neg_from(234)) == neg_from(357), 0); 219 | assert!(add(&neg_from(234), &neg_from(123)) == neg_from(357), 0); 220 | 221 | assert!(add(&from(123), &neg_from(123)) == zero(), 0); 222 | assert!(add(&neg_from(123), &from(123)) == zero(), 0); 223 | } 224 | 225 | #[test] 226 | fun test_sub() { 227 | assert!(sub(&from(123), &from(234)) == neg_from(111), 0); 228 | assert!(sub(&from(234), &from(123)) == from(111), 0); 229 | assert!(sub(&from(123), &neg_from(234)) == from(357), 0); 230 | assert!(sub(&neg_from(123), &from(234)) == neg_from(357), 0); 231 | assert!(sub(&neg_from(123), &neg_from(234)) == from(111), 0); 232 | assert!(sub(&neg_from(234), &neg_from(123)) == neg_from(111), 0); 233 | 234 | assert!(sub(&from(123), &from(123)) == zero(), 0); 235 | assert!(sub(&neg_from(123), &neg_from(123)) == zero(), 0); 236 | } 237 | 238 | #[test] 239 | fun test_mul() { 240 | assert!(mul(&from(123), &from(234)) == from(28782), 0); 241 | assert!(mul(&from(123), &neg_from(234)) == neg_from(28782), 0); 242 | assert!(mul(&neg_from(123), &from(234)) == neg_from(28782), 0); 243 | assert!(mul(&neg_from(123), &neg_from(234)) == from(28782), 0); 244 | } 245 | 246 | #[test] 247 | fun test_div() { 248 | assert!(div(&from(28781), &from(123)) == from(233), 0); 249 | assert!(div(&from(28781), &neg_from(123)) == neg_from(233), 0); 250 | assert!(div(&neg_from(28781), &from(123)) == neg_from(233), 0); 251 | assert!(div(&neg_from(28781), &neg_from(123)) == from(233), 0); 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /aptos/sources/math.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | /// @title math 4 | /// @dev Standard math utilities missing in the Move language (for `u64`). 5 | module movemate::math { 6 | const ROUNDING_DOWN: u8 = 0; // Toward negative infinity 7 | const ROUNDING_UP: u8 = 0; // Toward infinity 8 | const ROUNDING_ZERO: u8 = 0; // Toward zero 9 | const SCALAR: u64 = 1 << 16; 10 | 11 | /// @dev Returns the largest of two numbers. 12 | public fun max(a: u64, b: u64): u64 { 13 | if (a >= b) a else b 14 | } 15 | 16 | /// @dev Returns the smallest of two numbers. 17 | public fun min(a: u64, b: u64): u64 { 18 | if (a < b) a else b 19 | } 20 | 21 | /// @dev Returns the average of two numbers. The result is rounded towards zero. 22 | public fun average(a: u64, b: u64): u64 { 23 | // (a + b) / 2 can overflow. 24 | (a & b) + (a ^ b) / 2 25 | } 26 | 27 | /// @dev Returns the ceiling of the division of two numbers. 28 | /// This differs from standard division with `/` in that it rounds up instead of rounding down. 29 | public fun ceil_div(a: u64, b: u64): u64 { 30 | // (a + b - 1) / b can overflow on addition, so we distribute. 31 | if (a == 0) 0 else (a - 1) / b + 1 32 | } 33 | 34 | /// @dev Returns a to the power of b. 35 | public fun exp(a: u64, b: u64): u64 { 36 | let c = 1; 37 | 38 | while (b > 0) { 39 | if (b & 1 > 0) c = c * a; 40 | b = b >> 1; 41 | a = a * a; 42 | }; 43 | 44 | c 45 | } 46 | 47 | /// @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down. 48 | /// Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). 49 | /// Costs only 9 gas in comparison to the 16 gas `sui::math::sqrt` costs (tested on Aptos). 50 | public fun sqrt(a: u64): u64 { 51 | if (a == 0) { 52 | return 0 53 | }; 54 | 55 | // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. 56 | // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have 57 | // `msb(a) <= a < 2*msb(a)`. 58 | // We also know that `k`, the position of the most significant bit, is such that `msb(a) = 2**k`. 59 | // This gives `2**k < a <= 2**(k+1)` => `2**(k/2) <= sqrt(a) < 2 ** (k/2+1)`. 60 | // Using an algorithm similar to the msb computation, we are able to compute `result = 2**(k/2)` which is a 61 | // good first approximation of `sqrt(a)` with at least 1 correct bit. 62 | let result = 1; 63 | let x = a; 64 | if (x >> 32 > 0) { 65 | x = x >> 32; 66 | result = result << 16; 67 | }; 68 | if (x >> 16 > 0) { 69 | x = x >> 16; 70 | result = result << 8; 71 | }; 72 | if (x >> 8 > 0) { 73 | x = x >> 8; 74 | result = result << 4; 75 | }; 76 | if (x >> 4 > 0) { 77 | x = x >> 4; 78 | result = result << 2; 79 | }; 80 | if (x >> 2 > 0) { 81 | result = result << 1; 82 | }; 83 | 84 | // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, 85 | // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at 86 | // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision 87 | // into the expected uint128 result. 88 | result = (result + a / result) >> 1; 89 | result = (result + a / result) >> 1; 90 | result = (result + a / result) >> 1; 91 | result = (result + a / result) >> 1; 92 | result = (result + a / result) >> 1; 93 | result = (result + a / result) >> 1; 94 | result = (result + a / result) >> 1; 95 | min(result, a / result) 96 | } 97 | 98 | /// @notice Calculates sqrt(a), following the selected rounding direction. 99 | public fun sqrt_rounding(a: u64, rounding: u8): u64 { 100 | let result = sqrt(a); 101 | if (rounding == ROUNDING_UP && result * result < a) { 102 | result = result + 1; 103 | }; 104 | result 105 | } 106 | 107 | /// @notice Calculates ax^2 + bx + c assuming all variables are scaled by 2**16. 108 | public fun quadratic(x: u64, a: u64, b: u64, c: u64): u64 { 109 | (exp(x, 2) / SCALAR * a / SCALAR) 110 | + (b * x / SCALAR) 111 | + c 112 | } 113 | 114 | #[test] 115 | fun test_exp() { 116 | assert!(exp(0, 0) == 1, 0); // TODO: Should this be undefined? 117 | assert!(exp(0, 1) == 0, 1); 118 | assert!(exp(0, 5) == 0, 2); 119 | 120 | assert!(exp(1, 0) == 1, 3); 121 | assert!(exp(1, 1) == 1, 4); 122 | assert!(exp(1, 5) == 1, 5); 123 | 124 | assert!(exp(2, 0) == 1, 6); 125 | assert!(exp(2, 1) == 2, 7); 126 | assert!(exp(2, 5) == 32, 8); 127 | 128 | assert!(exp(123, 0) == 1, 9); 129 | assert!(exp(123, 1) == 123, 10); 130 | assert!(exp(123, 5) == 28153056843, 11); 131 | 132 | assert!(exp(45, 6) == 8303765625, 12); 133 | } 134 | 135 | #[test] 136 | fun test_sqrt() { 137 | assert!(sqrt(0) == 0, 0); 138 | assert!(sqrt(1) == 1, 1); 139 | 140 | assert!(sqrt(2) == 1, 2); 141 | assert!(sqrt_rounding(2, ROUNDING_UP) == 2, 3); 142 | 143 | assert!(sqrt(169) == 13, 4); 144 | assert!(sqrt_rounding(169, ROUNDING_UP) == 13, 5); 145 | assert!(sqrt_rounding(170, ROUNDING_UP) == 14, 6); 146 | assert!(sqrt(195) == 13, 7); 147 | assert!(sqrt(196) == 14, 8); 148 | 149 | assert!(sqrt(55423988929) == 235423, 9); 150 | assert!(sqrt_rounding(55423988929, ROUNDING_UP) == 235423, 10); 151 | assert!(sqrt(55423988930) == 235423, 11); 152 | assert!(sqrt_rounding(55423988930, ROUNDING_UP) == 235424, 12); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /aptos/sources/math_safe_precise.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | /// @title math_safe_precise 4 | /// @dev Overflow-avoidant and precise math utilities missing in the Move language (for `u64`). 5 | /// Specifically: 6 | /// 1) `mul_div` calculates `a * b / c` but converts to `u128`s during calculations to avoid overflow. 7 | /// 2) `quadratic` is the same as `math::quadratic` but uses `2**32` as a scalar instead of `2**16` for more precise math (converting to `u128`s during calculations for safety). 8 | module movemate::math_safe_precise { 9 | use movemate::math_u128; 10 | 11 | const SCALAR: u64 = 1 << 32; 12 | const SCALAR_U128: u128 = 1 << 32; 13 | 14 | /// @notice Calculates `a * b / c` but converts to `u128`s for calculations to avoid overflow. 15 | public fun mul_div(a: u64, b: u64, c: u64): u64 { 16 | ((a as u128) * (b as u128) / (c as u128) as u64) 17 | } 18 | 19 | /// @notice Calculates `ax^2 + bx + c` assuming all variables are scaled by `2**32`. 20 | public fun quadratic(x: u64, a: u64, b: u64, c: u64): u64 { 21 | ( 22 | (math_u128::exp((x as u128), 2) / SCALAR_U128 * (a as u128) / SCALAR_U128) 23 | + ((b as u128) * (x as u128) / SCALAR_U128) 24 | + (c as u128) 25 | as u64) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /aptos/sources/math_u128.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | /// @title math_u128 4 | /// @dev Standard math utilities missing in the Move language (for `u128`). 5 | module movemate::math_u128 { 6 | const ROUNDING_DOWN: u8 = 0; // Toward negative infinity 7 | const ROUNDING_UP: u8 = 0; // Toward infinity 8 | const ROUNDING_ZERO: u8 = 0; // Toward zero 9 | const SCALAR: u128 = 1 << 32; 10 | 11 | /// @dev Returns the largest of two numbers. 12 | public fun max(a: u128, b: u128): u128 { 13 | if (a >= b) a else b 14 | } 15 | 16 | /// @dev Returns the smallest of two numbers. 17 | public fun min(a: u128, b: u128): u128 { 18 | if (a < b) a else b 19 | } 20 | 21 | /// @dev Returns the average of two numbers. The result is rounded towards zero. 22 | public fun average(a: u128, b: u128): u128 { 23 | // (a + b) / 2 can overflow. 24 | (a & b) + (a ^ b) / 2 25 | } 26 | 27 | /// @dev Returns the ceiling of the division of two numbers. 28 | /// This differs from standard division with `/` in that it rounds up instead of rounding down. 29 | public fun ceil_div(a: u128, b: u128): u128 { 30 | // (a + b - 1) / b can overflow on addition, so we distribute. 31 | if (a == 0) 0 else (a - 1) / b + 1 32 | } 33 | 34 | /// @dev Returns a to the power of b. 35 | public fun exp(a: u128, b: u128): u128 { 36 | let c = 1; 37 | 38 | while (b > 0) { 39 | if (b & 1 > 0) c = c * a; 40 | b = b >> 1; 41 | a = a * a; 42 | }; 43 | 44 | c 45 | } 46 | 47 | /// @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down. 48 | /// Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). 49 | /// Costs only 9 gas in comparison to the 16 gas `sui::math::sqrt` costs (tested on Aptos). 50 | public fun sqrt(a: u128): u128 { 51 | if (a == 0) { 52 | return 0 53 | }; 54 | 55 | // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. 56 | // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have 57 | // `msb(a) <= a < 2*msb(a)`. 58 | // We also know that `k`, the position of the most significant bit, is such that `msb(a) = 2**k`. 59 | // This gives `2**k < a <= 2**(k+1)` => `2**(k/2) <= sqrt(a) < 2 ** (k/2+1)`. 60 | // Using an algorithm similar to the msb computation, we are able to compute `result = 2**(k/2)` which is a 61 | // good first approximation of `sqrt(a)` with at least 1 correct bit. 62 | let result = 1; 63 | let x = a; 64 | if (x >> 64 > 0) { 65 | x = x >> 64; 66 | result = result << 32; 67 | }; 68 | if (x >> 32 > 0) { 69 | x = x >> 32; 70 | result = result << 16; 71 | }; 72 | if (x >> 16 > 0) { 73 | x = x >> 16; 74 | result = result << 8; 75 | }; 76 | if (x >> 8 > 0) { 77 | x = x >> 8; 78 | result = result << 4; 79 | }; 80 | if (x >> 4 > 0) { 81 | x = x >> 4; 82 | result = result << 2; 83 | }; 84 | if (x >> 2 > 0) { 85 | result = result << 1; 86 | }; 87 | 88 | // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, 89 | // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at 90 | // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision 91 | // into the expected uint128 result. 92 | result = (result + a / result) >> 1; 93 | result = (result + a / result) >> 1; 94 | result = (result + a / result) >> 1; 95 | result = (result + a / result) >> 1; 96 | result = (result + a / result) >> 1; 97 | result = (result + a / result) >> 1; 98 | result = (result + a / result) >> 1; 99 | min(result, a / result) 100 | } 101 | 102 | /// @notice Calculates sqrt(a), following the selected rounding direction. 103 | public fun sqrt_rounding(a: u128, rounding: u8): u128 { 104 | let result = sqrt(a); 105 | if (rounding == ROUNDING_UP && result * result < a) { 106 | result = result + 1; 107 | }; 108 | result 109 | } 110 | 111 | /// @notice Calculates ax^2 + bx + c assuming all variables are scaled by 2**32. 112 | public fun quadratic(x: u128, a: u128, b: u128, c: u128): u128 { 113 | (exp(x, 2) / SCALAR * a / SCALAR) 114 | + (b * x / SCALAR) 115 | + c 116 | } 117 | 118 | #[test] 119 | fun test_exp() { 120 | assert!(exp(0, 0) == 1, 0); // TODO: Should this be undefined? 121 | assert!(exp(0, 1) == 0, 1); 122 | assert!(exp(0, 5) == 0, 2); 123 | 124 | assert!(exp(1, 0) == 1, 3); 125 | assert!(exp(1, 1) == 1, 4); 126 | assert!(exp(1, 5) == 1, 5); 127 | 128 | assert!(exp(2, 0) == 1, 6); 129 | assert!(exp(2, 1) == 2, 7); 130 | assert!(exp(2, 5) == 32, 8); 131 | 132 | assert!(exp(123, 0) == 1, 9); 133 | assert!(exp(123, 1) == 123, 10); 134 | assert!(exp(123, 5) == 28153056843, 11); 135 | 136 | assert!(exp(45, 6) == 8303765625, 12); 137 | } 138 | 139 | #[test] 140 | fun test_sqrt() { 141 | assert!(sqrt(0) == 0, 0); 142 | assert!(sqrt(1) == 1, 1); 143 | 144 | assert!(sqrt(2) == 1, 2); 145 | assert!(sqrt_rounding(2, ROUNDING_UP) == 2, 3); 146 | 147 | assert!(sqrt(169) == 13, 4); 148 | assert!(sqrt_rounding(169, ROUNDING_UP) == 13, 5); 149 | assert!(sqrt_rounding(170, ROUNDING_UP) == 14, 6); 150 | assert!(sqrt(195) == 13, 7); 151 | assert!(sqrt(196) == 14, 8); 152 | 153 | assert!(sqrt(55423988929) == 235423, 9); 154 | assert!(sqrt_rounding(55423988929, ROUNDING_UP) == 235423, 10); 155 | assert!(sqrt(55423988930) == 235423, 11); 156 | assert!(sqrt_rounding(55423988930, ROUNDING_UP) == 235424, 12); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /aptos/sources/merkle_proof.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Based on: OpenZeppelin Contracts (last updated v4.7.0) (utils/cryptography/MerkleProof.sol) 3 | 4 | /// @title merkle_proof 5 | /// @dev These functions deal with verification of Merkle Tree proofs. 6 | /// The proofs can be generated using the JavaScript library 7 | /// https://github.com/miguelmota/merkletreejs[merkletreejs]. 8 | /// Note: the hashing algorithm should be sha256 and pair sorting should be enabled. 9 | /// Example code below: 10 | /// const { MerkleTree } = require('merkletreejs') 11 | /// const SHA256 = require('crypto-js/sha256') 12 | /// const leaves = ['a', 'b', 'c'].map(x => SHA256(x)) 13 | /// const tree = new MerkleTree(leaves, SHA256, { sortPairs: true }) 14 | /// const root = tree.getRoot().toString('hex') 15 | /// const leaf = SHA256('a') 16 | /// const proof = tree.getProof(leaf) 17 | /// console.log(tree.verify(proof, leaf, root)) // true 18 | /// TODO: Unit tests for multi-proof verification. 19 | module movemate::merkle_proof { 20 | use std::error; 21 | use std::hash; 22 | use std::vector; 23 | 24 | use movemate::vectors; 25 | 26 | /// @dev When an invalid multi-proof is supplied. Proof flags length must equal proof length + leaves length - 1. 27 | const EINVALID_MULTI_PROOF: u64 = 0; 28 | 29 | /// @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree 30 | /// defined by `root`. For this, a `proof` must be provided, containing 31 | /// sibling hashes on the branch from the leaf to the root of the tree. Each 32 | /// pair of leaves and each pair of pre-images are assumed to be sorted. 33 | public fun verify( 34 | proof: &vector>, 35 | root: vector, 36 | leaf: vector 37 | ): bool { 38 | process_proof(proof, leaf) == root 39 | } 40 | 41 | /// @dev Returns the rebuilt hash obtained by traversing a Merkle tree up 42 | /// from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt 43 | /// hash matches the root of the tree. When processing the proof, the pairs 44 | /// of leafs & pre-images are assumed to be sorted. 45 | fun process_proof(proof: &vector>, leaf: vector): vector { 46 | let computed_hash = leaf; 47 | let proof_length = vector::length(proof); 48 | let i = 0; 49 | 50 | while (i < proof_length) { 51 | computed_hash = hash_pair(computed_hash, *vector::borrow(proof, i)); 52 | i = i + 1; 53 | }; 54 | 55 | computed_hash 56 | } 57 | 58 | /// @dev Returns true if the `leaves` can be proved to be a part of a Merkle tree defined by 59 | /// `root`, according to `proof` and `proofFlags` as described in {processMultiProof}. 60 | public fun multi_proof_verify( 61 | proof: &vector>, 62 | proof_flags: &vector, 63 | root: vector, 64 | leaves: &vector> 65 | ): bool { 66 | process_multi_proof(proof, proof_flags, leaves) == root 67 | } 68 | 69 | /// @dev Returns the root of a tree reconstructed from `leaves` and the sibling nodes in `proof`, 70 | /// consuming from one or the other at each step according to the instructions given by 71 | /// `proofFlags`. 72 | fun process_multi_proof( 73 | proof: &vector>, 74 | proof_flags: &vector, 75 | leaves: &vector>, 76 | ): vector { 77 | // This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by 78 | // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the 79 | // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of 80 | // the merkle tree. 81 | let leaves_len = vector::length(leaves); 82 | let total_hashes = vector::length(proof_flags); 83 | 84 | // Check proof validity. 85 | assert!(leaves_len + vector::length(proof) - 1 == total_hashes, error::invalid_argument(EINVALID_MULTI_PROOF)); 86 | 87 | // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using 88 | // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". 89 | let hashes = vector::empty>(); 90 | let leaf_pos = 0; 91 | let hash_pos = 0; 92 | let proof_pos = 0; 93 | // At each step, we compute the next hash using two values: 94 | // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we 95 | // get the next hash. 96 | // - depending on the flag, either another value for the "main queue" (merging branches) or an element from the 97 | // `proof` array. 98 | let i = 0; 99 | 100 | while (i < total_hashes) { 101 | let a = if (leaf_pos < leaves_len) { 102 | leaf_pos = leaf_pos + 1; 103 | *vector::borrow(leaves, leaf_pos) 104 | } else { 105 | hash_pos = hash_pos + 1; 106 | *vector::borrow(&hashes, hash_pos) 107 | }; 108 | 109 | let b = if (*vector::borrow(proof_flags, i)) { 110 | if (leaf_pos < leaves_len) { 111 | leaf_pos = leaf_pos + 1; 112 | *vector::borrow(leaves, leaf_pos) 113 | } else { 114 | hash_pos = hash_pos + 1; 115 | *vector::borrow(&hashes, hash_pos) 116 | } 117 | } else { 118 | proof_pos = proof_pos + 1; 119 | *vector::borrow(proof, proof_pos) 120 | }; 121 | 122 | vector::push_back(&mut hashes, hash_pair(a, b)); 123 | i = i + 1; 124 | }; 125 | 126 | if (total_hashes > 0) { 127 | *vector::borrow(&hashes, total_hashes - 1) 128 | } else if (leaves_len > 0) { 129 | *vector::borrow(leaves, 0) 130 | } else { 131 | *vector::borrow(proof, 0) 132 | } 133 | } 134 | 135 | fun hash_pair(a: vector, b: vector): vector { 136 | if (vectors::lt(&a, &b)) efficient_hash(a, b) else efficient_hash(b, a) 137 | } 138 | 139 | fun efficient_hash(a: vector, b: vector): vector { 140 | vector::append(&mut a, b); 141 | hash::sha2_256(a) 142 | } 143 | 144 | #[test] 145 | fun test_verify() { 146 | let proof = vector::empty>(); 147 | vector::push_back(&mut proof, x"3e23e8160039594a33894f6564e1b1348bbd7a0088d42c4acb73eeaed59c009d"); 148 | vector::push_back(&mut proof, x"2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6"); 149 | let root = x"aea2dd4249dcecf97ca6a1556db7f21ebd6a40bbec0243ca61b717146a08c347"; 150 | let leaf = x"ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"; 151 | assert!(verify(&proof, root, leaf), 0); 152 | } 153 | 154 | #[test] 155 | fun test_verify_bad_proof() { 156 | let proof = vector::empty>(); 157 | vector::push_back(&mut proof, x"3e23e8160039594a33894f6564e1b1349bbd7a0088d42c4acb73eeaed59c009d"); 158 | vector::push_back(&mut proof, x"2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6"); 159 | let root = x"aea2dd4249dcecf97ca6a1556db7f21ebd6a40bbec0243ca61b717146a08c347"; 160 | let leaf = x"ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"; 161 | assert!(!verify(&proof, root, leaf), 0); 162 | } 163 | 164 | #[test] 165 | fun test_verify_bad_root() { 166 | let proof = vector::empty>(); 167 | vector::push_back(&mut proof, x"3e23e8160039594a33894f6564e1b1348bbd7a0088d42c4acb73eeaed59c009d"); 168 | vector::push_back(&mut proof, x"2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6"); 169 | let root = x"aea9dd4249dcecf97ca6a1556db7f21ebd6a40bbec0243ca61b717146a08c347"; 170 | let leaf = x"ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"; 171 | assert!(!verify(&proof, root, leaf), 0); 172 | } 173 | 174 | #[test] 175 | fun test_verify_bad_leaf() { 176 | let proof = vector::empty>(); 177 | vector::push_back(&mut proof, x"3e23e8160039594a33894f6564e1b1348bbd7a0088d42c4acb73eeaed59c009d"); 178 | vector::push_back(&mut proof, x"2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6"); 179 | let root = x"aea2dd4249dcecf97ca6a1556db7f21ebd6a40bbec0243ca61b717146a08c347"; 180 | let leaf = x"ca978112ca1bbdc1fac231b39a23dc4da786eff8147c4e72b9807785afee48bb"; 181 | assert!(!verify(&proof, root, leaf), 0); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /aptos/sources/pseudorandom.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Based on: https://github.com/starcoinorg/starcoin-framework-commons/blob/main/sources/PseudoRandom.move 3 | 4 | /// @title pseudorandom 5 | /// @notice A pseudo random module on-chain. 6 | /// @dev Warning: 7 | /// The random mechanism in smart contracts is different from 8 | /// that in traditional programming languages. The value generated 9 | /// by random is predictable to Miners, so it can only be used in 10 | /// simple scenarios where Miners have no incentive to cheat. If 11 | /// large amounts of money are involved, DO NOT USE THIS MODULE to 12 | /// generate random numbers; try a more secure way. 13 | module movemate::pseudorandom { 14 | use std::bcs; 15 | use std::error; 16 | use std::hash; 17 | use std::signer; 18 | use std::vector; 19 | 20 | use aptos_framework::account; 21 | use aptos_framework::block; 22 | use aptos_framework::timestamp; 23 | use aptos_framework::transaction_context; 24 | 25 | use movemate::bcd; 26 | 27 | const ENOT_ROOT: u64 = 0; 28 | const EHIGH_ARG_GREATER_THAN_LOW_ARG: u64 = 1; 29 | 30 | /// Resource that wraps an integer counter. 31 | struct Counter has key { 32 | value: u64 33 | } 34 | 35 | /// Publish a `Counter` resource with value `i` under the given `root` account. 36 | public entry fun init(root: &signer) { 37 | // "Pack" (create) a Counter resource. This is a privileged operation that 38 | // can only be done inside the module that declares the `Counter` resource 39 | assert!(signer::address_of(root) == @movemate, error::permission_denied(ENOT_ROOT)); 40 | move_to(root, Counter { value: 0 }) 41 | } 42 | 43 | /// Increment the value of `addr`'s `Counter` resource. 44 | fun increment(): u64 acquires Counter { 45 | let c_ref = &mut borrow_global_mut(@movemate).value; 46 | *c_ref = *c_ref + 1; 47 | *c_ref 48 | } 49 | 50 | /// Acquire a seed using: the hash of the counter, block height, timestamp, script hash, sender address, and sender sequence number. 51 | fun seed(_sender: &address): vector acquires Counter { 52 | let counter = increment(); 53 | let counter_bytes = bcs::to_bytes(&counter); 54 | 55 | let height: u64 = block::get_current_block_height(); 56 | let height_bytes: vector = bcs::to_bytes(&height); 57 | 58 | let timestamp: u64 = timestamp::now_microseconds(); 59 | let timestamp_bytes: vector = bcs::to_bytes(×tamp); 60 | 61 | let script_hash: vector = transaction_context::get_script_hash(); 62 | 63 | let sender_bytes: vector = bcs::to_bytes(_sender); 64 | 65 | let sequence_number: u64 = account::get_sequence_number(*_sender); 66 | let sequence_number_bytes = bcs::to_bytes(&sequence_number); 67 | 68 | let info: vector = vector::empty(); 69 | vector::append(&mut info, counter_bytes); 70 | vector::append(&mut info, height_bytes); 71 | vector::append(&mut info, timestamp_bytes); 72 | vector::append(&mut info, script_hash); 73 | vector::append(&mut info, sender_bytes); 74 | vector::append(&mut info, sequence_number_bytes); 75 | 76 | let hash: vector = hash::sha3_256(info); 77 | hash 78 | } 79 | 80 | /// Acquire a seed using: the hash of the counter, block height, timestamp, and script hash. 81 | fun seed_no_sender(): vector acquires Counter { 82 | let counter = increment(); 83 | let counter_bytes = bcs::to_bytes(&counter); 84 | 85 | let height: u64 = block::get_current_block_height(); 86 | let height_bytes: vector = bcs::to_bytes(&height); 87 | 88 | let timestamp: u64 = timestamp::now_microseconds(); 89 | let timestamp_bytes: vector = bcs::to_bytes(×tamp); 90 | 91 | let script_hash: vector = transaction_context::get_script_hash(); 92 | 93 | let info: vector = vector::empty(); 94 | vector::append(&mut info, counter_bytes); 95 | vector::append(&mut info, height_bytes); 96 | vector::append(&mut info, timestamp_bytes); 97 | vector::append(&mut info, script_hash); 98 | 99 | let hash: vector = hash::sha3_256(info); 100 | hash 101 | } 102 | 103 | /// Generate a random u128 104 | public fun rand_u128_with_seed(_seed: vector): u128 { 105 | bcd::bytes_to_u128(_seed) 106 | } 107 | 108 | /// Generate a random integer range in [low, high). 109 | public fun rand_u128_range_with_seed(_seed: vector, low: u128, high: u128): u128 { 110 | assert!(high > low, error::invalid_argument(EHIGH_ARG_GREATER_THAN_LOW_ARG)); 111 | let value = rand_u128_with_seed(_seed); 112 | (value % (high - low)) + low 113 | } 114 | 115 | /// Generate a random u64 116 | public fun rand_u64_with_seed(_seed: vector): u64 { 117 | bcd::bytes_to_u64(_seed) 118 | } 119 | 120 | /// Generate a random integer range in [low, high). 121 | public fun rand_u64_range_with_seed(_seed: vector, low: u64, high: u64): u64 { 122 | assert!(high > low, error::invalid_argument(EHIGH_ARG_GREATER_THAN_LOW_ARG)); 123 | let value = rand_u64_with_seed(_seed); 124 | (value % (high - low)) + low 125 | } 126 | 127 | public fun rand_u128(sender: &address): u128 acquires Counter { rand_u128_with_seed(seed(sender)) } 128 | public fun rand_u128_range(sender: &address, low: u128, high: u128): u128 acquires Counter { rand_u128_range_with_seed(seed(sender), low, high) } 129 | public fun rand_u64(sender: &address): u64 acquires Counter { rand_u64_with_seed(seed(sender)) } 130 | public fun rand_u64_range(sender: &address, low: u64, high: u64): u64 acquires Counter { rand_u64_range_with_seed(seed(sender), low, high) } 131 | 132 | public fun rand_u128_no_sender(): u128 acquires Counter { rand_u128_with_seed(seed_no_sender()) } 133 | public fun rand_u128_range_no_sender(low: u128, high: u128): u128 acquires Counter { rand_u128_range_with_seed(seed_no_sender(), low, high) } 134 | public fun rand_u64_no_sender(): u64 acquires Counter { rand_u64_with_seed(seed_no_sender()) } 135 | public fun rand_u64_range_no_sender(low: u64, high: u64): u64 acquires Counter { rand_u64_range_with_seed(seed_no_sender(), low, high) } 136 | } 137 | -------------------------------------------------------------------------------- /aptos/sources/to_string.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Source: https://github.com/starcoinorg/starcoin-framework-commons/blob/main/sources/StringUtil.move 3 | 4 | /// @title to_string 5 | /// @notice `u128` to `String` conversion utilities. 6 | module movemate::to_string { 7 | use std::string::{Self, String}; 8 | use std::vector; 9 | 10 | const HEX_SYMBOLS: vector = b"0123456789abcdef"; 11 | 12 | // Maximum value of u128, i.e. 2 ** 128 - 1 13 | // Source: https://github.com/move-language/move/blob/a86f31415b9a18867b5edaed6f915a39b8c2ef40/language/move-prover/doc/user/spec-lang.md?plain=1#L214 14 | const MAX_U128: u128 = 340282366920938463463374607431768211455; 15 | 16 | /// @dev Converts a `u128` to its `ascii::String` decimal representation. 17 | public fun to_string(value: u128): String { 18 | if (value == 0) { 19 | return string::utf8(b"0") 20 | }; 21 | let buffer = vector::empty(); 22 | while (value != 0) { 23 | vector::push_back(&mut buffer, ((48 + value % 10) as u8)); 24 | value = value / 10; 25 | }; 26 | vector::reverse(&mut buffer); 27 | string::utf8(buffer) 28 | } 29 | 30 | /// Converts a `u128` to its `string::String` hexadecimal representation. 31 | public fun to_hex_string(value: u128): String { 32 | if (value == 0) { 33 | return string::utf8(b"0x00") 34 | }; 35 | let temp: u128 = value; 36 | let length: u128 = 0; 37 | while (temp != 0) { 38 | length = length + 1; 39 | temp = temp >> 8; 40 | }; 41 | to_hex_string_fixed_length(value, length) 42 | } 43 | 44 | /// Converts a `u128` to its `string::String` hexadecimal representation with fixed length (in whole bytes). 45 | /// so the returned String is `2 * length + 2`(with '0x') in size 46 | public fun to_hex_string_fixed_length(value: u128, length: u128): String { 47 | let buffer = vector::empty(); 48 | 49 | let i: u128 = 0; 50 | while (i < length * 2) { 51 | vector::push_back(&mut buffer, *vector::borrow(&mut HEX_SYMBOLS, (value & 0xf as u64))); 52 | value = value >> 4; 53 | i = i + 1; 54 | }; 55 | assert!(value == 0, 1); 56 | vector::append(&mut buffer, b"x0"); 57 | vector::reverse(&mut buffer); 58 | string::utf8(buffer) 59 | } 60 | 61 | /// @dev Converts a `vector` to its `string::String` hexadecimal representation. 62 | /// so the returned String is `2 * length + 2`(with '0x') in size 63 | public fun bytes_to_hex_string(bytes: &vector): String { 64 | let length = vector::length(bytes); 65 | let buffer = b"0x"; 66 | 67 | let i: u64 = 0; 68 | while (i < length) { 69 | let byte = *vector::borrow(bytes, i); 70 | vector::push_back(&mut buffer, *vector::borrow(&mut HEX_SYMBOLS, (byte >> 4 & 0xf as u64))); 71 | vector::push_back(&mut buffer, *vector::borrow(&mut HEX_SYMBOLS, (byte & 0xf as u64))); 72 | i = i + 1; 73 | }; 74 | string::utf8(buffer) 75 | } 76 | 77 | #[test] 78 | fun test_to_string() { 79 | assert!(b"0" == *string::bytes(&to_string(0)), 1); 80 | assert!(b"1" == *string::bytes(&to_string(1)), 1); 81 | assert!(b"257" == *string::bytes(&to_string(257)), 1); 82 | assert!(b"10" == *string::bytes(&to_string(10)), 1); 83 | assert!(b"12345678" == *string::bytes(&to_string(12345678)), 1); 84 | assert!(b"340282366920938463463374607431768211455" == *string::bytes(&to_string(MAX_U128)), 1); 85 | } 86 | 87 | #[test] 88 | fun test_to_hex_string() { 89 | assert!(b"0x00" == *string::bytes(&to_hex_string(0)), 1); 90 | assert!(b"0x01" == *string::bytes(&to_hex_string(1)), 1); 91 | assert!(b"0x0101" == *string::bytes(&to_hex_string(257)), 1); 92 | assert!(b"0xbc614e" == *string::bytes(&to_hex_string(12345678)), 1); 93 | assert!(b"0xffffffffffffffffffffffffffffffff" == *string::bytes(&to_hex_string(MAX_U128)), 1); 94 | } 95 | 96 | #[test] 97 | fun test_to_hex_string_fixed_length() { 98 | assert!(b"0x00" == *string::bytes(&to_hex_string_fixed_length(0, 1)), 1); 99 | assert!(b"0x01" == *string::bytes(&to_hex_string_fixed_length(1, 1)), 1); 100 | assert!(b"0x10" == *string::bytes(&to_hex_string_fixed_length(16, 1)), 1); 101 | assert!(b"0x0011" == *string::bytes(&to_hex_string_fixed_length(17, 2)), 1); 102 | assert!(b"0x0000bc614e" == *string::bytes(&to_hex_string_fixed_length(12345678, 5)), 1); 103 | assert!(b"0xffffffffffffffffffffffffffffffff" == *string::bytes(&to_hex_string_fixed_length(MAX_U128, 16)), 1); 104 | } 105 | 106 | #[test] 107 | fun test_bytes_to_hex_string() { 108 | assert!(b"0x00" == *string::bytes(&bytes_to_hex_string(&x"00")), 1); 109 | assert!(b"0x01" == *string::bytes(&bytes_to_hex_string(&x"01")), 1); 110 | assert!(b"0x1924bacf" == *string::bytes(&bytes_to_hex_string(&x"1924bacf")), 1); 111 | assert!(b"0x8324445443539749823794832789472398748932794327743277489327498732" == *string::bytes(&bytes_to_hex_string(&x"8324445443539749823794832789472398748932794327743277489327498732")), 1); 112 | assert!(b"0xbfee823235227564" == *string::bytes(&bytes_to_hex_string(&x"bfee823235227564")), 1); 113 | assert!(b"0xffffffffffffffffffffffffffffffff" == *string::bytes(&bytes_to_hex_string(&x"ffffffffffffffffffffffffffffffff")), 1); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /aptos/sources/vectors.move: -------------------------------------------------------------------------------- 1 | /// @title vectors 2 | /// @notice Vector utilities. 3 | /// @dev TODO: Fuzz testing? 4 | module movemate::vectors { 5 | use std::error; 6 | use std::vector; 7 | 8 | use movemate::math; 9 | 10 | /// @dev When you supply vectors of different lengths to a function requiring equal-length vectors. 11 | /// TODO: Support variable length vectors? 12 | const EVECTOR_LENGTH_MISMATCH: u64 = 0; 13 | 14 | /// @dev Searches a sorted `vec` and returns the first index that contains 15 | /// a value greater or equal to `element`. If no such index exists (i.e. all 16 | /// values in the vector are strictly less than `element`), the vector length is 17 | /// returned. Time complexity O(log n). 18 | /// `vec` is expected to be sorted in ascending order, and to contain no 19 | /// repeated elements. 20 | public fun find_upper_bound(vec: &vector, element: u64): u64 { 21 | if (vector::length(vec) == 0) { 22 | return 0 23 | }; 24 | 25 | let low = 0; 26 | let high = vector::length(vec); 27 | 28 | while (low < high) { 29 | let mid = math::average(low, high); 30 | 31 | // Note that mid will always be strictly less than high (i.e. it will be a valid vector index) 32 | // because math::average rounds down (it does integer division with truncation). 33 | if (*vector::borrow(vec, mid) > element) { 34 | high = mid; 35 | } else { 36 | low = mid + 1; 37 | } 38 | }; 39 | 40 | // At this point `low` is the exclusive upper bound. We will return the inclusive upper bound. 41 | if (low > 0 && *vector::borrow(vec, low - 1) == element) { 42 | low - 1 43 | } else { 44 | low 45 | } 46 | } 47 | 48 | public fun lt(a: &vector, b: &vector): bool { 49 | let i = 0; 50 | let len = vector::length(a); 51 | assert!(len == vector::length(b), error::invalid_argument(EVECTOR_LENGTH_MISMATCH)); 52 | 53 | while (i < len) { 54 | let aa = *vector::borrow(a, i); 55 | let bb = *vector::borrow(b, i); 56 | if (aa < bb) return true; 57 | if (aa > bb) return false; 58 | i = i + 1; 59 | }; 60 | 61 | false 62 | } 63 | 64 | public fun gt(a: &vector, b: &vector): bool { 65 | let i = 0; 66 | let len = vector::length(a); 67 | assert!(len == vector::length(b), error::invalid_argument(EVECTOR_LENGTH_MISMATCH)); 68 | 69 | while (i < len) { 70 | let aa = *vector::borrow(a, i); 71 | let bb = *vector::borrow(b, i); 72 | if (aa > bb) return true; 73 | if (aa < bb) return false; 74 | i = i + 1; 75 | }; 76 | 77 | false 78 | } 79 | 80 | public fun lte(a: &vector, b: &vector): bool { 81 | let i = 0; 82 | let len = vector::length(a); 83 | assert!(len == vector::length(b), error::invalid_argument(EVECTOR_LENGTH_MISMATCH)); 84 | 85 | while (i < len) { 86 | let aa = *vector::borrow(a, i); 87 | let bb = *vector::borrow(b, i); 88 | if (aa < bb) return true; 89 | if (aa > bb) return false; 90 | i = i + 1; 91 | }; 92 | 93 | true 94 | } 95 | 96 | public fun gte(a: &vector, b: &vector): bool { 97 | let i = 0; 98 | let len = vector::length(a); 99 | assert!(len == vector::length(b), error::invalid_argument(EVECTOR_LENGTH_MISMATCH)); 100 | 101 | while (i < len) { 102 | let aa = *vector::borrow(a, i); 103 | let bb = *vector::borrow(b, i); 104 | if (aa > bb) return true; 105 | if (aa < bb) return false; 106 | i = i + 1; 107 | }; 108 | 109 | true 110 | } 111 | 112 | #[test] 113 | fun test_find_upper_bound() { 114 | let vec = vector::empty(); 115 | vector::push_back(&mut vec, 33); 116 | vector::push_back(&mut vec, 66); 117 | vector::push_back(&mut vec, 99); 118 | vector::push_back(&mut vec, 100); 119 | vector::push_back(&mut vec, 123); 120 | vector::push_back(&mut vec, 222); 121 | vector::push_back(&mut vec, 233); 122 | vector::push_back(&mut vec, 244); 123 | assert!(find_upper_bound(&vec, 223) == 6, 0); 124 | } 125 | 126 | #[test] 127 | fun test_lt() { 128 | assert!(lt(&x"19853428", &x"19853429"), 0); 129 | assert!(lt(&x"32432023", &x"32432323"), 1); 130 | assert!(!lt(&x"83975792", &x"83975492"), 2); 131 | assert!(!lt(&x"83975492", &x"83975492"), 3); 132 | } 133 | 134 | #[test] 135 | fun test_gt() { 136 | assert!(gt(&x"17432844", &x"17432843"), 0); 137 | assert!(gt(&x"79847429", &x"79847329"), 1); 138 | assert!(!gt(&x"19849334", &x"19849354"), 2); 139 | assert!(!gt(&x"19849354", &x"19849354"), 3); 140 | } 141 | 142 | #[test] 143 | fun test_not_gt() { 144 | assert!(lte(&x"23789179", &x"23789279"), 0); 145 | assert!(lte(&x"23789279", &x"23789279"), 1); 146 | assert!(!lte(&x"13258445", &x"13258444"), 2); 147 | assert!(!lte(&x"13258454", &x"13258444"), 3); 148 | } 149 | 150 | #[test] 151 | fun test_lte() { 152 | assert!(lte(&x"23789179", &x"23789279"), 0); 153 | assert!(lte(&x"23789279", &x"23789279"), 1); 154 | assert!(!lte(&x"13258445", &x"13258444"), 2); 155 | assert!(!lte(&x"13258454", &x"13258444"), 3); 156 | } 157 | 158 | #[test] 159 | fun test_gte() { 160 | assert!(gte(&x"14329932", &x"14329832"), 0); 161 | assert!(gte(&x"14329832", &x"14329832"), 1); 162 | assert!(!gte(&x"12654586", &x"12654587"), 2); 163 | assert!(!gte(&x"12654577", &x"12654587"), 3); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /aptos/sources/virtual_block.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | /// @title virtual_block 4 | /// @dev This module allows the creation of virtual blocks with transactions sorted by fees, turning transaction latency auctions into fee auctions. 5 | /// Once you've created a new mempool (specifying a miner fee rate and a block time/delay), simply add entries to the block, 6 | /// mine the entries (for a miner fee), and repeat. Extract mempool fees as necessary. 7 | module movemate::virtual_block { 8 | use std::error; 9 | use std::vector; 10 | 11 | use aptos_framework::coin::{Self, Coin}; 12 | use aptos_framework::timestamp; 13 | 14 | use movemate::crit_bit::{Self, CB}; 15 | 16 | /// @dev When trying to mine a block before the block time has passed. 17 | const EBLOCK_TIME_NOT_PASSED: u64 = 0; 18 | 19 | /// @notice Struct for a virtual block with entries sorted by bids. 20 | struct Mempool has store { 21 | blocks: vector>>, 22 | current_block_bids: Coin, 23 | last_block_timestamp: u64, 24 | mempool_fees: Coin, 25 | miner_fee_rate: u64, 26 | block_time: u64 27 | } 28 | 29 | /// @notice Creates a new mempool (specifying miner fee rate and block time/delay). 30 | public fun new_mempool(miner_fee_rate: u64, block_time: u64): Mempool { 31 | Mempool { 32 | blocks: vector::singleton>>(crit_bit::empty>()), 33 | current_block_bids: coin::zero(), 34 | last_block_timestamp: timestamp::now_microseconds(), 35 | mempool_fees: coin::zero(), 36 | miner_fee_rate, 37 | block_time 38 | } 39 | } 40 | 41 | /// @notice Extracts all fees accrued by a mempool. 42 | public fun extract_mempool_fees(mempool: &mut Mempool): Coin { 43 | coin::extract_all(&mut mempool.mempool_fees) 44 | } 45 | 46 | /// @notice Adds an entry to the latest virtual block. 47 | public fun add_entry(mempool: &mut Mempool, entry: EntryType, bid: Coin) { 48 | // Add bid to block 49 | let bid_value = (coin::value(&bid) as u128); 50 | coin::merge(&mut mempool.current_block_bids, bid); 51 | 52 | // Add entry to tree 53 | let len = vector::length(&mempool.blocks); 54 | let block = vector::borrow_mut(&mut mempool.blocks, len - 1); 55 | if (crit_bit::has_key(block, bid_value)) vector::push_back(crit_bit::borrow_mut(block, bid_value), entry) 56 | else crit_bit::insert(block, bid_value, vector::singleton(entry)); 57 | } 58 | 59 | /// @notice Validates the block time and distributes fees. 60 | public fun mine_entries(mempool: &mut Mempool, miner: address): CB> { 61 | // Validate time now >= last block time + block delay 62 | let now = timestamp::now_microseconds(); 63 | assert!(now >= mempool.last_block_timestamp + mempool.block_time, error::invalid_state(EBLOCK_TIME_NOT_PASSED)); 64 | 65 | // Withdraw miner_fee_rate / 2**16 to the miner 66 | let miner_fee = coin::value(&mempool.current_block_bids) * mempool.miner_fee_rate / (1 << 16); 67 | coin::deposit(miner, coin::extract(&mut mempool.current_block_bids, miner_fee)); 68 | 69 | // Send the rest to the mempool admin 70 | coin::merge(&mut mempool.mempool_fees, coin::extract_all(&mut mempool.current_block_bids)); 71 | 72 | // Get last block 73 | let last_block = vector::pop_back(&mut mempool.blocks); 74 | 75 | // Create next block 76 | vector::push_back(&mut mempool.blocks, crit_bit::empty>()); 77 | *&mut mempool.last_block_timestamp = now; 78 | 79 | // Return entries of last block 80 | last_block 81 | } 82 | 83 | #[test_only] 84 | struct FakeEntry has store, drop { 85 | stuff: u64 86 | } 87 | 88 | #[test_only] 89 | struct FakeMoney { } 90 | 91 | #[test_only] 92 | struct FakeMoneyCapabilities has key { 93 | mint_cap: coin::MintCapability, 94 | burn_cap: coin::BurnCapability, 95 | freeze_cap: coin::FreezeCapability, 96 | } 97 | 98 | #[test_only] 99 | struct TempMempool has key { 100 | mempool: Mempool 101 | } 102 | 103 | #[test_only] 104 | fun fast_forward_microseconds(timestamp_microseconds: u64) { 105 | timestamp::update_global_time_for_test(timestamp::now_microseconds() + timestamp_microseconds); 106 | } 107 | 108 | #[test(miner = @0x1000, coin_creator = @movemate, aptos_framework = @aptos_framework)] 109 | public entry fun test_end_to_end(miner: signer, coin_creator: signer, aptos_framework: signer) { 110 | // start the clock 111 | timestamp::set_time_has_started_for_testing(&aptos_framework); 112 | 113 | // mint fake coin 114 | let (burn_cap, freeze_cap, mint_cap) = coin::initialize( 115 | &coin_creator, 116 | std::string::utf8(b"Fake Money A"), 117 | std::string::utf8(b"FMA"), 118 | 6, 119 | true 120 | ); 121 | let coin_in_a = coin::mint(1234000000, &mint_cap); 122 | let coin_in_b = coin::mint(5678000000, &mint_cap); 123 | 124 | // create mempool 125 | let mempool = new_mempool(1 << 14, 5000000); // 25% miner fee rate and 5 second block time 126 | 127 | // add entry 128 | add_entry(&mut mempool, FakeEntry { stuff: 1234 }, coin_in_a); 129 | 130 | // fast forward and add entry 131 | fast_forward_microseconds(3000000); 132 | add_entry(&mut mempool, FakeEntry { stuff: 5678 }, coin_in_b); 133 | 134 | // fast forward and mine block 135 | fast_forward_microseconds(3000000); 136 | let miner_address = std::signer::address_of(&miner); 137 | coin::register_for_test(&miner); 138 | let cb = mine_entries(&mut mempool, miner_address); 139 | assert!(coin::balance(miner_address) == (1234000000 + 5678000000) / 4, 0); 140 | 141 | // Loop through highest to lowest bid 142 | let last_bid = 0xFFFFFFFFFFFFFFFF; 143 | 144 | while (!crit_bit::is_empty(&cb)) { 145 | let bid = crit_bit::max_key(&cb); 146 | assert!(bid < last_bid, 1); 147 | crit_bit::pop(&mut cb, bid); 148 | }; 149 | 150 | crit_bit::destroy_empty(cb); 151 | 152 | // extract mempool fees 153 | let mempool_fees = extract_mempool_fees(&mut mempool); 154 | assert!(coin::value(&mempool_fees) == (1234000000 + 5678000000) - ((1234000000 + 5678000000) / 4), 2); 155 | 156 | // clean up: we can't drop coins so we burn them 157 | coin::burn(mempool_fees, &burn_cap); 158 | 159 | // clean up: we can't drop mint/burn caps so we store them 160 | move_to(&coin_creator, FakeMoneyCapabilities { 161 | burn_cap, 162 | freeze_cap, 163 | mint_cap 164 | }); 165 | move_to(&coin_creator, TempMempool { 166 | mempool, 167 | }); 168 | } 169 | 170 | #[test(miner = @0x1000, coin_creator = @movemate, aptos_framework = @aptos_framework)] 171 | #[expected_failure(abort_code = 0x30000)] 172 | public entry fun test_mine_before_time(miner: signer, coin_creator: signer, aptos_framework: signer) { 173 | // start the clock 174 | timestamp::set_time_has_started_for_testing(&aptos_framework); 175 | 176 | // mint fake coin 177 | let (burn_cap, freeze_cap, mint_cap) = coin::initialize( 178 | &coin_creator, 179 | std::string::utf8(b"Fake Money A"), 180 | std::string::utf8(b"FMA"), 181 | 6, 182 | true 183 | ); 184 | let coin_in = coin::mint(1234000000, &mint_cap); 185 | 186 | // create mempool 187 | let mempool = new_mempool(1 << 14, 5000000); // 25% miner fee rate and 5 second block time 188 | 189 | // add entry 190 | add_entry(&mut mempool, FakeEntry { stuff: 1234 }, coin_in); 191 | 192 | // fast forward and try to mine 193 | fast_forward_microseconds(3000000); 194 | let miner_address = std::signer::address_of(&miner); 195 | coin::register_for_test(&miner); 196 | let cb = mine_entries(&mut mempool, miner_address); 197 | 198 | // destroy cb tree 199 | crit_bit::pop(&mut cb, 1234); 200 | crit_bit::destroy_empty(cb); 201 | 202 | // clean up: we can't drop mint/burn caps so we store them 203 | move_to(&coin_creator, FakeMoneyCapabilities { 204 | burn_cap, 205 | freeze_cap, 206 | mint_cap 207 | }); 208 | move_to(&coin_creator, TempMempool { 209 | mempool, 210 | }); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "movemate", 3 | "description": "Movemate is the Solmate of Move (i.e., smart contract building blocks) built for Aptos and Sui.", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/pentagonxyz/movemate.git" 11 | }, 12 | "author": "", 13 | "license": "UNLICENSED", 14 | "bugs": { 15 | "url": "https://github.com/pentagonxyz/movemate/issues" 16 | }, 17 | "homepage": "https://github.com/pentagonxyz/movemate#readme" 18 | } 19 | -------------------------------------------------------------------------------- /sui/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "Movemate" 3 | version = "1.0.0" 4 | authors = ["David Lucid "] 5 | 6 | [addresses] 7 | std = "0x1" 8 | sui = "0x2" 9 | movemate = "0x0" 10 | 11 | [dependencies.MoveStdlib] 12 | git = "https://github.com/MystenLabs/sui.git" 13 | subdir = "crates/sui-framework/deps/move-stdlib" 14 | rev = "fa973184a13579108a45965f7ff0fc3f2e2dc753" 15 | 16 | [dependencies.Sui] 17 | git = "https://github.com/MystenLabs/sui.git" 18 | subdir = "crates/sui-framework" 19 | rev = "fa973184a13579108a45965f7ff0fc3f2e2dc753" 20 | -------------------------------------------------------------------------------- /sui/sources/acl.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | /// @title acl 4 | /// @notice Multi-role access control list (ACL). 5 | /// @dev Maps addresses to `u128`s with each bit representing the presence of (or lack of) each role. 6 | module movemate::acl { 7 | use std::errors; 8 | 9 | use sui::vec_map::{Self, VecMap}; 10 | 11 | /// @dev When attempting to add/remove a role >= 128. 12 | const EROLE_NUMBER_TOO_LARGE: u64 = 0; 13 | 14 | /// @dev Maps addresses to `u128`s with each bit representing the presence of (or lack of) each role. 15 | struct ACL has store { 16 | permissions: VecMap 17 | } 18 | 19 | /// @notice Create a new ACL (access control list). 20 | public fun new(): ACL { 21 | ACL { permissions: vec_map::empty() } 22 | } 23 | 24 | /// @notice Check if a member has a role in the ACL. 25 | public fun has_role(acl: &ACL, member: address, role: u8): bool { 26 | assert!(role < 128, errors::invalid_argument(EROLE_NUMBER_TOO_LARGE)); 27 | vec_map::contains(&acl.permissions, &member) && *vec_map::get(&acl.permissions, &member) & (1 << role) > 0 28 | } 29 | 30 | /// @notice Set all roles for a member in the ACL. 31 | /// @param permissions Permissions for a member, represented as a `u128` with each bit representing the presence of (or lack of) each role. 32 | public fun set_roles(acl: &mut ACL, member: address, permissions: u128) { 33 | if (vec_map::contains(&acl.permissions, &member)) *vec_map::get_mut(&mut acl.permissions, &member) = permissions 34 | else vec_map::insert(&mut acl.permissions, member, permissions); 35 | } 36 | 37 | /// @notice Add a role for a member in the ACL. 38 | public fun add_role(acl: &mut ACL, member: address, role: u8) { 39 | assert!(role < 128, errors::invalid_argument(EROLE_NUMBER_TOO_LARGE)); 40 | if (vec_map::contains(&acl.permissions, &member)) { 41 | let perms = vec_map::get_mut(&mut acl.permissions, &member); 42 | *perms = *perms | (1 << role); 43 | } else { 44 | vec_map::insert(&mut acl.permissions, member, 1 << role); 45 | } 46 | } 47 | 48 | /// @notice Revoke a role for a member in the ACL. 49 | public fun remove_role(acl: &mut ACL, member: address, role: u8) { 50 | assert!(role < 128, errors::invalid_argument(EROLE_NUMBER_TOO_LARGE)); 51 | if (vec_map::contains(&acl.permissions, &member)) { 52 | let perms = vec_map::get_mut(&mut acl.permissions, &member); 53 | *perms = *perms - (1 << role); 54 | } 55 | } 56 | 57 | #[test_only] 58 | struct TestACL has key { 59 | acl: ACL 60 | } 61 | 62 | #[test] 63 | fun test_end_to_end() { 64 | let acl = new(); 65 | add_role(&mut acl, @0x1234, 12); 66 | add_role(&mut acl, @0x1234, 99); 67 | add_role(&mut acl, @0x1234, 88); 68 | add_role(&mut acl, @0x1234, 123); 69 | add_role(&mut acl, @0x1234, 2); 70 | add_role(&mut acl, @0x1234, 1); 71 | remove_role(&mut acl, @0x1234, 2); 72 | set_roles(&mut acl, @0x5678, (1 << 123) | (1 << 2) | (1 << 1)); 73 | let i = 0; 74 | while (i < 128) { 75 | let has = has_role(&acl, @0x1234, i); 76 | assert!(if (i == 12 || i == 99 || i == 88 || i == 123 || i == 1) has else !has, 0); 77 | has = has_role(&acl, @0x5678, i); 78 | assert!(if (i == 123 || i == 2 || i == 1) has else !has, 1); 79 | i = i + 1; 80 | }; 81 | 82 | // can't drop so must store 83 | sui::transfer::share_object(TestACL { acl }); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /sui/sources/bcd.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Source: https://github.com/starcoinorg/starcoin-framework-commons/blob/main/sources/PseudoRandom.move 3 | 4 | /// @title bcd 5 | /// @notice BCD = Binary canoncial DEserialization. 6 | module movemate::bcd { 7 | use std::vector; 8 | 9 | public fun bytes_to_u128(bytes: vector): u128 { 10 | let value = 0u128; 11 | let i = 0u64; 12 | while (i < 16) { 13 | value = value | ((*vector::borrow(&bytes, i) as u128) << ((8 * (15 - i)) as u8)); 14 | i = i + 1; 15 | }; 16 | return value 17 | } 18 | 19 | public fun bytes_to_u64(bytes: vector): u64 { 20 | let value = 0u64; 21 | let i = 0u64; 22 | while (i < 8) { 23 | value = value | ((*vector::borrow(&bytes, i) as u64) << ((8 * (7 - i)) as u8)); 24 | i = i + 1; 25 | }; 26 | return value 27 | } 28 | 29 | #[test] 30 | fun test_bytes_to_u64() { 31 | // binary: 01010001 11010011 10101111 11001100 11111101 00001001 10001110 11001101 32 | // bytes = [81, 211, 175, 204, 253, 9, 142, 205]; 33 | let dec = 5896249632111562445; 34 | 35 | let bytes = vector::empty(); 36 | vector::push_back(&mut bytes, 81); 37 | vector::push_back(&mut bytes, 211); 38 | vector::push_back(&mut bytes, 175); 39 | vector::push_back(&mut bytes, 204); 40 | vector::push_back(&mut bytes, 253); 41 | vector::push_back(&mut bytes, 9); 42 | vector::push_back(&mut bytes, 142); 43 | vector::push_back(&mut bytes, 205); 44 | 45 | let value = bytes_to_u64(bytes); 46 | assert!(value == dec, 101); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /sui/sources/bloom_filter.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Based on: https://github.com/wanseob/solidity-bloom-filter/blob/master/contracts/BloomFilter.sol 3 | 4 | /// @title bloom_filter 5 | /// @dev Probabilistic data structure for checking if an element is part of a set. 6 | module movemate::bloom_filter { 7 | use std::errors; 8 | use std::hash; 9 | use std::vector; 10 | 11 | use movemate::u256::{Self, U256}; 12 | 13 | const EHASH_COUNT_IS_ZERO: u64 = 0; 14 | const EVECTOR_LENGTH_NOT_32: u64 = 1; 15 | 16 | struct Filter has copy, drop, store { 17 | bitmap: U256, 18 | hash_count: u8 19 | } 20 | 21 | /// @dev It returns how many times it should be hashed, when the expected number of input items is `_item_num`. 22 | /// @param _item_num Expected number of input items 23 | public fun get_hash_count(_item_num: u64): u8 { 24 | let num_of_hash = (256 * 144) / (_item_num * 100) + 1; 25 | if (num_of_hash < 256) (num_of_hash as u8) else 255 26 | } 27 | 28 | /// @dev It returns updated bitmap when a new item is added into the bitmap 29 | /// @param _bitmap Original bitmap 30 | /// @param _hash_count How many times to hash. You should use the same value with the one which is used for the original bitmap. 31 | /// @param _item Hash value of an item 32 | public fun add_to_bitmap(_bitmap: U256, _hash_count: u8, _item: vector): U256 { 33 | assert!(_hash_count > 0, errors::invalid_argument(EHASH_COUNT_IS_ZERO)); 34 | assert!(vector::length(&_item) == 32, errors::invalid_argument(EVECTOR_LENGTH_NOT_32)); 35 | let i: u8 = 0; 36 | vector::push_back(&mut _item, 0); 37 | while (i < _hash_count) { 38 | *vector::borrow_mut(&mut _item, 32) = i; 39 | let position = vector::pop_back(&mut hash::sha2_256(_item)); 40 | let digest = u256::shl(u256::from_u128(1), position); 41 | _bitmap = u256::or(&_bitmap, &digest); 42 | i = i + 1; 43 | }; 44 | _bitmap 45 | } 46 | 47 | /// @dev It returns it may exist or definitely not exist. 48 | /// @param _bitmap Original bitmap 49 | /// @param _hash_count How many times to hash. You should use the same value with the one which is used for the original bitmap. 50 | /// @param _item Hash value of an item 51 | public fun false_positive(_bitmap: U256, _hash_count: u8, _item: vector): bool { 52 | assert!(_hash_count > 0, errors::invalid_argument(EHASH_COUNT_IS_ZERO)); 53 | assert!(vector::length(&_item) == 32, errors::invalid_argument(EVECTOR_LENGTH_NOT_32)); 54 | let i: u8 = 0; 55 | vector::push_back(&mut _item, 0); 56 | while (i < _hash_count) { 57 | *vector::borrow_mut(&mut _item, 32) = i; 58 | let position = vector::pop_back(&mut hash::sha2_256(_item)); 59 | let digest = u256::shl(u256::from_u128(1), position); 60 | if (_bitmap != u256::or(&_bitmap, &digest)) return false; 61 | i = i + 1; 62 | }; 63 | true 64 | } 65 | 66 | /// @dev It initialize the Filter struct. It sets the appropriate hash count for the expected number of item 67 | /// @param _itemNum Expected number of items to be added 68 | public fun new(_item_num: u64): Filter { 69 | Filter { 70 | bitmap: u256::zero(), 71 | hash_count: get_hash_count(_item_num) 72 | } 73 | } 74 | 75 | /// @dev It updates the bitmap of the filter using the given item value 76 | /// @param _item Hash value of an item 77 | public fun add(_filter: &mut Filter, _item: vector) { 78 | *&mut _filter.bitmap = add_to_bitmap(_filter.bitmap, _filter.hash_count, _item); 79 | } 80 | 81 | /// @dev It returns the filter may include the item or definitely now include it. 82 | /// @param _item Hash value of an item 83 | public fun check(_filter: &Filter, _item: vector): bool { 84 | false_positive(_filter.bitmap, _filter.hash_count, _item) 85 | } 86 | 87 | #[test] 88 | public fun test_end_to_end() { 89 | // Test init: check hash count 90 | let filter = new(10); 91 | assert!(filter.hash_count == 37, 0); // Hash count should equal 37 92 | 93 | // Test adding elements 94 | add(&mut filter, b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); 95 | let bitmap_a = filter.bitmap; 96 | add(&mut filter, b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); 97 | let bitmap_b = filter.bitmap; 98 | assert!(bitmap_b == bitmap_a, 1); // Adding same item should not update the bitmap 99 | add(&mut filter, b"cccccccccccccccccccccccccccccccc"); 100 | let bitmap_c = filter.bitmap; 101 | assert!(bitmap_c != bitmap_b, 2); // Adding different item should update the bitmap 102 | 103 | // Test checking for inclusion 104 | let included = b"abcdefghij"; 105 | let not_included = b"klmnopqrst"; 106 | let i = 0; 107 | while (i < 10) { 108 | let key = hash::sha2_256(vector::singleton(*vector::borrow(&included, i))); 109 | add(&mut filter, key); 110 | i = i + 1; 111 | }; 112 | let j = 0; 113 | while (j < 10) { 114 | let key = hash::sha2_256(vector::singleton(*vector::borrow(&included, j))); 115 | let false_positive = check(&filter, key); 116 | // It may exist or not 117 | assert!(false_positive, 3); // Should return false positive 118 | j = j + 1; 119 | }; 120 | let k = 0; 121 | while (k < 10) { 122 | let key = hash::sha2_256(vector::singleton(*vector::borrow(¬_included, k))); 123 | let false_positive = check(&filter, key); 124 | // It definitely does not exist 125 | assert!(!false_positive, 4); // Should return definitely not exist 126 | k = k + 1; 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /sui/sources/box.move: -------------------------------------------------------------------------------- 1 | /// @title box 2 | /// @notice Generalized box for transferring objects that only have `store` but not `key`. 3 | module movemate::box { 4 | use sui::object::{Self, Info}; 5 | use sui::transfer; 6 | use sui::tx_context::{Self, TxContext}; 7 | 8 | struct Box has key, store { 9 | info: Info, 10 | obj: T 11 | } 12 | 13 | struct PrivateBox has key, store { 14 | info: Info, 15 | obj: T, 16 | sender: address 17 | } 18 | 19 | /// @dev Stores the sent object in an box object. 20 | /// @param recipient The destination address of the box object. 21 | public entry fun box(recipient: address, obj_in: T, ctx: &mut TxContext) { 22 | let box = Box { 23 | info: object::new(ctx), 24 | obj: obj_in 25 | }; 26 | transfer::transfer(box, recipient); 27 | } 28 | 29 | /// @dev Unboxes the object inside the box. 30 | public fun unbox(box: Box): T { 31 | let Box { 32 | info: info, 33 | obj: obj, 34 | } = box; 35 | object::delete(info); 36 | obj 37 | } 38 | 39 | /// @dev Stores the sent object in a private box object. (Private box = stores the sender in the sender property.) 40 | /// @param recipient The destination address of the box object. 41 | public entry fun box_private(recipient: address, obj_in: T, ctx: &mut TxContext) { 42 | let box = PrivateBox { 43 | info: object::new(ctx), 44 | obj: obj_in, 45 | sender: tx_context::sender(ctx) 46 | }; 47 | transfer::transfer(box, recipient); 48 | } 49 | 50 | /// @dev Unboxes the object inside the private box. (Private box = stores the sender in the sender property.) 51 | public fun unbox_private(box: PrivateBox): T { 52 | let PrivateBox { 53 | info: info, 54 | obj: obj, 55 | sender: _ 56 | } = box; 57 | object::delete(info); 58 | obj 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /sui/sources/escrow.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | /// @title escrow 4 | /// @dev Basic escrow module: holds an object designated for a recipient until the sender approves withdrawal. 5 | module movemate::escrow { 6 | use sui::object::{Self, Info}; 7 | use sui::transfer; 8 | use sui::tx_context::TxContext; 9 | 10 | struct Escrow has key { 11 | info: Info, 12 | recipient: address, 13 | obj: T 14 | } 15 | 16 | /// @dev Stores the sent object in an escrow object. 17 | /// @param recipient The destination address of the escrowed object. 18 | public entry fun escrow(sender: address, recipient: address, obj_in: T, ctx: &mut TxContext) { 19 | let escrow = Escrow { 20 | info: object::new(ctx), 21 | recipient, 22 | obj: obj_in 23 | }; 24 | transfer::transfer(escrow, sender); 25 | } 26 | 27 | /// @dev Transfers escrowed object to the recipient. 28 | public entry fun transfer(escrow: Escrow) { 29 | let Escrow { 30 | info: info, 31 | recipient: recipient, 32 | obj: obj, 33 | } = escrow; 34 | object::delete(info); 35 | transfer::transfer(obj, recipient); 36 | } 37 | 38 | #[test_only] 39 | use sui::test_scenario; 40 | 41 | #[test_only] 42 | const TEST_SENDER_ADDR: address = @0xA11CE; 43 | 44 | #[test_only] 45 | const TEST_RECIPIENT_ADDR: address = @0xB0B; 46 | 47 | #[test_only] 48 | struct FakeObject has key, store { 49 | info: Info, 50 | data: u64 51 | } 52 | 53 | #[test] 54 | public fun test_end_to_end() { 55 | let scenario = &mut test_scenario::begin(&TEST_SENDER_ADDR); 56 | escrow(TEST_SENDER_ADDR, TEST_RECIPIENT_ADDR, FakeObject { info: object::new(test_scenario::ctx(scenario)), data: 1234 }, test_scenario::ctx(scenario)); 57 | test_scenario::next_tx(scenario, &TEST_SENDER_ADDR); 58 | let escrow = test_scenario::take_owned>(scenario); 59 | transfer(escrow); 60 | test_scenario::next_tx(scenario, &TEST_RECIPIENT_ADDR); 61 | let obj = test_scenario::take_owned(scenario); 62 | assert!(obj.data == 1234, 0); 63 | test_scenario::return_owned(scenario, obj); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /sui/sources/escrow_shared.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | /// @title escrow_shared 4 | /// @dev Basic escrow module with refunds and an arbitrator: holds an object designated for a recipient until the sender approves withdrawal, the recipient refunds the sender, or the arbitrator does one of the two. 5 | module movemate::escrow_shared { 6 | use std::errors; 7 | use std::option::{Self, Option}; 8 | 9 | use sui::object::{Self, Info}; 10 | use sui::transfer; 11 | use sui::tx_context::{Self, TxContext}; 12 | 13 | /// @dev When trying to transfer escrowed object to the recipient but you are not the sender. 14 | const ENOT_SENDER: u64 = 0; 15 | 16 | /// @dev When trying to refund escrowed object to the sender but you are not the recipient. 17 | const ENOT_RECIPIENT: u64 = 1; 18 | 19 | /// @dev When trying to arbitrate an escrowed object but you are not the arbitrator. 20 | const ENOT_ARBITRATOR: u64 = 2; 21 | 22 | struct Escrow has key { 23 | info: Info, 24 | sender: address, 25 | recipient: address, 26 | arbitrator: Option
, 27 | obj: Option 28 | } 29 | 30 | /// @dev Stores the sent object in an escrow object. 31 | /// @param recipient The destination address of the escrowed object. 32 | public entry fun escrow(sender: address, recipient: address, arbitrator: Option
, obj_in: T, ctx: &mut TxContext) { 33 | let escrow = Escrow { 34 | info: object::new(ctx), 35 | sender, 36 | recipient, 37 | arbitrator, 38 | obj: option::some(obj_in) 39 | }; 40 | transfer::share_object(escrow); 41 | } 42 | 43 | /// @dev Transfers escrowed object to the recipient. 44 | public entry fun transfer(escrow: &mut Escrow, ctx: &mut TxContext) { 45 | assert!(tx_context::sender(ctx) == escrow.sender, errors::requires_address(ENOT_SENDER)); 46 | transfer::transfer(option::extract(&mut escrow.obj), escrow.recipient); 47 | } 48 | 49 | /// @dev Refunds escrowed object to the sender. 50 | public entry fun refund(escrow: &mut Escrow, ctx: &mut TxContext) { 51 | assert!(tx_context::sender(ctx) == escrow.recipient, errors::requires_address(ENOT_RECIPIENT)); 52 | transfer::transfer(option::extract(&mut escrow.obj), escrow.sender); 53 | } 54 | 55 | /// @dev Transfers escrowed object to the recipient. 56 | public entry fun transfer_arbitrated(escrow: &mut Escrow, ctx: &mut TxContext) { 57 | assert!(option::is_some(&escrow.arbitrator) && tx_context::sender(ctx) == *option::borrow(&escrow.arbitrator), errors::requires_address(ENOT_ARBITRATOR)); 58 | transfer::transfer(option::extract(&mut escrow.obj), escrow.recipient); 59 | } 60 | 61 | /// @dev Refunds escrowed object to the sender. 62 | public entry fun refund_arbitrated(escrow: &mut Escrow, ctx: &mut TxContext) { 63 | assert!(option::is_some(&escrow.arbitrator) && tx_context::sender(ctx) == *option::borrow(&escrow.arbitrator), errors::requires_address(ENOT_ARBITRATOR)); 64 | transfer::transfer(option::extract(&mut escrow.obj), escrow.sender); 65 | } 66 | 67 | #[test_only] 68 | use sui::test_scenario; 69 | 70 | #[test_only] 71 | const TEST_SENDER_ADDR: address = @0xA11CE; 72 | 73 | #[test_only] 74 | const TEST_RECIPIENT_ADDR: address = @0xB0B; 75 | 76 | #[test_only] 77 | const TEST_ARBITRATOR_ADDR: address = @0xDAD; 78 | 79 | #[test_only] 80 | struct FakeObject has key, store { 81 | info: Info, 82 | data: u64 83 | } 84 | 85 | #[test] 86 | public fun test_transfer() { 87 | let scenario = &mut test_scenario::begin(&TEST_SENDER_ADDR); 88 | escrow(TEST_SENDER_ADDR, TEST_RECIPIENT_ADDR, option::some(TEST_ARBITRATOR_ADDR), FakeObject { info: object::new(test_scenario::ctx(scenario)), data: 1234 }, test_scenario::ctx(scenario)); 89 | test_scenario::next_tx(scenario, &TEST_SENDER_ADDR); 90 | let escrow_wrapper = test_scenario::take_shared>(scenario); 91 | let escrow = test_scenario::borrow_mut(&mut escrow_wrapper); 92 | transfer(escrow, test_scenario::ctx(scenario)); 93 | test_scenario::return_shared(scenario, escrow_wrapper); 94 | test_scenario::next_tx(scenario, &TEST_RECIPIENT_ADDR); 95 | let obj = test_scenario::take_owned(scenario); 96 | assert!(obj.data == 1234, 0); 97 | test_scenario::return_owned(scenario, obj); 98 | } 99 | 100 | #[test] 101 | public fun test_refund() { 102 | let scenario = &mut test_scenario::begin(&TEST_SENDER_ADDR); 103 | escrow(TEST_SENDER_ADDR, TEST_RECIPIENT_ADDR, option::some(TEST_ARBITRATOR_ADDR), FakeObject { info: object::new(test_scenario::ctx(scenario)), data: 1234 }, test_scenario::ctx(scenario)); 104 | test_scenario::next_tx(scenario, &TEST_RECIPIENT_ADDR); 105 | let escrow_wrapper = test_scenario::take_shared>(scenario); 106 | let escrow = test_scenario::borrow_mut(&mut escrow_wrapper); 107 | refund(escrow, test_scenario::ctx(scenario)); 108 | test_scenario::return_shared(scenario, escrow_wrapper); 109 | test_scenario::next_tx(scenario, &TEST_SENDER_ADDR); 110 | let obj = test_scenario::take_owned(scenario); 111 | assert!(obj.data == 1234, 0); 112 | test_scenario::return_owned(scenario, obj); 113 | } 114 | 115 | #[test] 116 | public fun test_transfer_arbitrator() { 117 | let scenario = &mut test_scenario::begin(&TEST_SENDER_ADDR); 118 | escrow(TEST_SENDER_ADDR, TEST_RECIPIENT_ADDR, option::some(TEST_ARBITRATOR_ADDR), FakeObject { info: object::new(test_scenario::ctx(scenario)), data: 1234 }, test_scenario::ctx(scenario)); 119 | test_scenario::next_tx(scenario, &TEST_ARBITRATOR_ADDR); 120 | let escrow_wrapper = test_scenario::take_shared>(scenario); 121 | let escrow = test_scenario::borrow_mut(&mut escrow_wrapper); 122 | transfer_arbitrated(escrow, test_scenario::ctx(scenario)); 123 | test_scenario::return_shared(scenario, escrow_wrapper); 124 | test_scenario::next_tx(scenario, &TEST_RECIPIENT_ADDR); 125 | let obj = test_scenario::take_owned(scenario); 126 | assert!(obj.data == 1234, 0); 127 | test_scenario::return_owned(scenario, obj); 128 | } 129 | 130 | #[test] 131 | public fun test_refund_arbitrator() { 132 | let scenario = &mut test_scenario::begin(&TEST_SENDER_ADDR); 133 | escrow(TEST_SENDER_ADDR, TEST_RECIPIENT_ADDR, option::some(TEST_ARBITRATOR_ADDR), FakeObject { info: object::new(test_scenario::ctx(scenario)), data: 1234 }, test_scenario::ctx(scenario)); 134 | test_scenario::next_tx(scenario, &TEST_ARBITRATOR_ADDR); 135 | let escrow_wrapper = test_scenario::take_shared>(scenario); 136 | let escrow = test_scenario::borrow_mut(&mut escrow_wrapper); 137 | refund_arbitrated(escrow, test_scenario::ctx(scenario)); 138 | test_scenario::return_shared(scenario, escrow_wrapper); 139 | test_scenario::next_tx(scenario, &TEST_SENDER_ADDR); 140 | let obj = test_scenario::take_owned(scenario); 141 | assert!(obj.data == 1234, 0); 142 | test_scenario::return_owned(scenario, obj); 143 | } 144 | 145 | #[test] 146 | #[expected_failure(abort_code = 0x002)] 147 | public fun test_transfer_unauthorized() { 148 | let scenario = &mut test_scenario::begin(&TEST_SENDER_ADDR); 149 | escrow(TEST_SENDER_ADDR, TEST_RECIPIENT_ADDR, option::some(TEST_ARBITRATOR_ADDR), FakeObject { info: object::new(test_scenario::ctx(scenario)), data: 1234 }, test_scenario::ctx(scenario)); 150 | test_scenario::next_tx(scenario, &TEST_RECIPIENT_ADDR); 151 | let escrow_wrapper = test_scenario::take_shared>(scenario); 152 | let escrow = test_scenario::borrow_mut(&mut escrow_wrapper); 153 | transfer(escrow, test_scenario::ctx(scenario)); 154 | test_scenario::return_shared(scenario, escrow_wrapper); 155 | } 156 | 157 | #[test] 158 | #[expected_failure(abort_code = 0x102)] 159 | public fun test_refund_unauthorized() { 160 | let scenario = &mut test_scenario::begin(&TEST_SENDER_ADDR); 161 | escrow(TEST_SENDER_ADDR, TEST_RECIPIENT_ADDR, option::some(TEST_ARBITRATOR_ADDR), FakeObject { info: object::new(test_scenario::ctx(scenario)), data: 1234 }, test_scenario::ctx(scenario)); 162 | test_scenario::next_tx(scenario, &TEST_SENDER_ADDR); 163 | let escrow_wrapper = test_scenario::take_shared>(scenario); 164 | let escrow = test_scenario::borrow_mut(&mut escrow_wrapper); 165 | refund(escrow, test_scenario::ctx(scenario)); 166 | test_scenario::return_shared(scenario, escrow_wrapper); 167 | } 168 | 169 | #[test] 170 | #[expected_failure(abort_code = 0x202)] 171 | public fun test_transfer_arbitrator_unauthorized() { 172 | let scenario = &mut test_scenario::begin(&TEST_SENDER_ADDR); 173 | escrow(TEST_SENDER_ADDR, TEST_RECIPIENT_ADDR, option::some(TEST_ARBITRATOR_ADDR), FakeObject { info: object::new(test_scenario::ctx(scenario)), data: 1234 }, test_scenario::ctx(scenario)); 174 | test_scenario::next_tx(scenario, &TEST_RECIPIENT_ADDR); 175 | let escrow_wrapper = test_scenario::take_shared>(scenario); 176 | let escrow = test_scenario::borrow_mut(&mut escrow_wrapper); 177 | transfer_arbitrated(escrow, test_scenario::ctx(scenario)); 178 | test_scenario::return_shared(scenario, escrow_wrapper); 179 | } 180 | 181 | #[test] 182 | #[expected_failure(abort_code = 0x202)] 183 | public fun test_refund_arbitrator_unauthorized() { 184 | let scenario = &mut test_scenario::begin(&TEST_SENDER_ADDR); 185 | escrow(TEST_SENDER_ADDR, TEST_RECIPIENT_ADDR, option::some(TEST_ARBITRATOR_ADDR), FakeObject { info: object::new(test_scenario::ctx(scenario)), data: 1234 }, test_scenario::ctx(scenario)); 186 | test_scenario::next_tx(scenario, &TEST_SENDER_ADDR); 187 | let escrow_wrapper = test_scenario::take_shared>(scenario); 188 | let escrow = test_scenario::borrow_mut(&mut escrow_wrapper); 189 | refund_arbitrated(escrow, test_scenario::ctx(scenario)); 190 | test_scenario::return_shared(scenario, escrow_wrapper); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /sui/sources/i128.move: -------------------------------------------------------------------------------- 1 | /// @title i128 2 | /// @notice Signed 128-bit integers in Move. 3 | /// @dev TODO: Pass in params by value instead of by ref to make usage easier? 4 | module movemate::i128 { 5 | use std::errors; 6 | 7 | /// @dev Maximum I128 value as a u128. 8 | const MAX_I128_AS_U128: u128 = (1 << 127) - 1; 9 | 10 | /// @dev u128 with the first bit set. An `I128` is negative if this bit is set. 11 | const U128_WITH_FIRST_BIT_SET: u128 = 1 << 127; 12 | 13 | /// When both `U256` equal. 14 | const EQUAL: u8 = 0; 15 | 16 | /// When `a` is less than `b`. 17 | const LESS_THAN: u8 = 1; 18 | 19 | /// When `b` is greater than `b`. 20 | const GREATER_THAN: u8 = 2; 21 | 22 | /// @dev When trying to convert from a u128 > MAX_I128_AS_U128 to an I128. 23 | const ECONVERSION_FROM_U128_OVERFLOW: u64 = 0; 24 | 25 | /// @dev When trying to convert from an negative I128 to a u128. 26 | const ECONVERSION_TO_U128_UNDERFLOW: u64 = 1; 27 | 28 | /// @notice Struct representing a signed 128-bit integer. 29 | struct I128 has copy, drop, store { 30 | bits: u128 31 | } 32 | 33 | /// @notice Casts a `u128` to an `I128`. 34 | public fun from(x: u128): I128 { 35 | assert!(x <= MAX_I128_AS_U128, errors::invalid_argument(ECONVERSION_FROM_U128_OVERFLOW)); 36 | I128 { bits: x } 37 | } 38 | 39 | /// @notice Creates a new `I128` with value 0. 40 | public fun zero(): I128 { 41 | I128 { bits: 0 } 42 | } 43 | 44 | /// @notice Casts an `I128` to a `u128`. 45 | public fun as_u128(x: &I128): u128 { 46 | assert!(x.bits < U128_WITH_FIRST_BIT_SET, errors::invalid_argument(ECONVERSION_TO_U128_UNDERFLOW)); 47 | x.bits 48 | } 49 | 50 | /// @notice Whether or not `x` is equal to 0. 51 | public fun is_zero(x: &I128): bool { 52 | x.bits == 0 53 | } 54 | 55 | /// @notice Whether or not `x` is negative. 56 | public fun is_neg(x: &I128): bool { 57 | x.bits > U128_WITH_FIRST_BIT_SET 58 | } 59 | 60 | /// @notice Flips the sign of `x`. 61 | public fun neg(x: &I128): I128 { 62 | if (x.bits == 0) return *x; 63 | I128 { bits: if (x.bits < U128_WITH_FIRST_BIT_SET) x.bits | (1 << 127) else x.bits - (1 << 127) } 64 | } 65 | 66 | /// @notice Flips the sign of `x`. 67 | public fun neg_from(x: u128): I128 { 68 | let ret = from(x); 69 | if (ret.bits > 0) *&mut ret.bits = ret.bits | (1 << 127); 70 | ret 71 | } 72 | 73 | /// @notice Absolute value of `x`. 74 | public fun abs(x: &I128): I128 { 75 | if (x.bits < U128_WITH_FIRST_BIT_SET) *x else I128 { bits: x.bits - (1 << 127) } 76 | } 77 | 78 | /// @notice Compare `a` and `b`. 79 | public fun compare(a: &I128, b: &I128): u8 { 80 | if (a.bits == b.bits) return EQUAL; 81 | if (a.bits < U128_WITH_FIRST_BIT_SET) { 82 | // A is positive 83 | if (b.bits < U128_WITH_FIRST_BIT_SET) { 84 | // B is positive 85 | return if (a.bits > b.bits) GREATER_THAN else LESS_THAN 86 | } else { 87 | // B is negative 88 | return GREATER_THAN 89 | } 90 | } else { 91 | // A is negative 92 | if (b.bits < U128_WITH_FIRST_BIT_SET) { 93 | // B is positive 94 | return LESS_THAN 95 | } else { 96 | // B is negative 97 | return if (a.bits > b.bits) LESS_THAN else GREATER_THAN 98 | } 99 | } 100 | } 101 | 102 | /// @notice Add `a + b`. 103 | public fun add(a: &I128, b: &I128): I128 { 104 | if (a.bits >> 127 == 0) { 105 | // A is positive 106 | if (b.bits >> 127 == 0) { 107 | // B is positive 108 | return I128 { bits: a.bits + b.bits } 109 | } else { 110 | // B is negative 111 | if (b.bits - (1 << 127) <= a.bits) return I128 { bits: a.bits - (b.bits - (1 << 127)) }; // Return positive 112 | return I128 { bits: b.bits - a.bits } // Return negative 113 | } 114 | } else { 115 | // A is negative 116 | if (b.bits >> 127 == 0) { 117 | // B is positive 118 | if (a.bits - (1 << 127) <= b.bits) return I128 { bits: b.bits - (a.bits - (1 << 127)) }; // Return positive 119 | return I128 { bits: a.bits - b.bits } // Return negative 120 | } else { 121 | // B is negative 122 | return I128 { bits: a.bits + (b.bits - (1 << 127)) } 123 | } 124 | } 125 | } 126 | 127 | /// @notice Subtract `a - b`. 128 | public fun sub(a: &I128, b: &I128): I128 { 129 | if (a.bits >> 127 == 0) { 130 | // A is positive 131 | if (b.bits >> 127 == 0) { 132 | // B is positive 133 | if (a.bits >= b.bits) return I128 { bits: a.bits - b.bits }; // Return positive 134 | return I128 { bits: (1 << 127) | (b.bits - a.bits) } // Return negative 135 | } else { 136 | // B is negative 137 | return I128 { bits: a.bits + (b.bits - (1 << 127)) } // Return negative 138 | } 139 | } else { 140 | // A is negative 141 | if (b.bits >> 127 == 0) { 142 | // B is positive 143 | return I128 { bits: a.bits + b.bits } // Return negative 144 | } else { 145 | // B is negative 146 | if (b.bits >= a.bits) return I128 { bits: b.bits - a.bits }; // Return positive 147 | return I128 { bits: a.bits - (b.bits - (1 << 127)) } // Return negative 148 | } 149 | } 150 | } 151 | 152 | /// @notice Multiply `a * b`. 153 | public fun mul(a: &I128, b: &I128): I128 { 154 | if (a.bits >> 127 == 0) { 155 | // A is positive 156 | if (b.bits >> 127 == 0) { 157 | // B is positive 158 | return I128 { bits: a.bits * b.bits } // Return positive 159 | } else { 160 | // B is negative 161 | return I128 { bits: (1 << 127) | (a.bits * (b.bits - (1 << 127))) } // Return negative 162 | } 163 | } else { 164 | // A is negative 165 | if (b.bits >> 127 == 0) { 166 | // B is positive 167 | return I128 { bits: (1 << 127) | (b.bits * (a.bits - (1 << 127))) } // Return negative 168 | } else { 169 | // B is negative 170 | return I128 { bits: (a.bits - (1 << 127)) * (b.bits - (1 << 127)) } // Return positive 171 | } 172 | } 173 | } 174 | 175 | /// @notice Divide `a / b`. 176 | public fun div(a: &I128, b: &I128): I128 { 177 | if (a.bits >> 127 == 0) { 178 | // A is positive 179 | if (b.bits >> 127 == 0) { 180 | // B is positive 181 | return I128 { bits: a.bits / b.bits } // Return positive 182 | } else { 183 | // B is negative 184 | return I128 { bits: (1 << 127) | (a.bits / (b.bits - (1 << 127))) } // Return negative 185 | } 186 | } else { 187 | // A is negative 188 | if (b.bits >> 127 == 0) { 189 | // B is positive 190 | return I128 { bits: (1 << 127) | ((a.bits - (1 << 127)) / b.bits) } // Return negative 191 | } else { 192 | // B is negative 193 | return I128 { bits: (a.bits - (1 << 127)) / (b.bits - (1 << 127)) } // Return positive 194 | } 195 | } 196 | } 197 | 198 | #[test] 199 | fun test_compare() { 200 | assert!(compare(&from(123), &from(123)) == EQUAL, 0); 201 | assert!(compare(&neg_from(123), &neg_from(123)) == EQUAL, 0); 202 | assert!(compare(&from(234), &from(123)) == GREATER_THAN, 0); 203 | assert!(compare(&from(123), &from(234)) == LESS_THAN, 0); 204 | assert!(compare(&neg_from(234), &neg_from(123)) == LESS_THAN, 0); 205 | assert!(compare(&neg_from(123), &neg_from(234)) == GREATER_THAN, 0); 206 | assert!(compare(&from(123), &neg_from(234)) == GREATER_THAN, 0); 207 | assert!(compare(&neg_from(123), &from(234)) == LESS_THAN, 0); 208 | assert!(compare(&from(234), &neg_from(123)) == GREATER_THAN, 0); 209 | assert!(compare(&neg_from(234), &from(123)) == LESS_THAN, 0); 210 | } 211 | 212 | #[test] 213 | fun test_add() { 214 | assert!(add(&from(123), &from(234)) == from(357), 0); 215 | assert!(add(&from(123), &neg_from(234)) == neg_from(111), 0); 216 | assert!(add(&from(234), &neg_from(123)) == from(111), 0); 217 | assert!(add(&neg_from(123), &from(234)) == from(111), 0); 218 | assert!(add(&neg_from(123), &neg_from(234)) == neg_from(357), 0); 219 | assert!(add(&neg_from(234), &neg_from(123)) == neg_from(357), 0); 220 | 221 | assert!(add(&from(123), &neg_from(123)) == zero(), 0); 222 | assert!(add(&neg_from(123), &from(123)) == zero(), 0); 223 | } 224 | 225 | #[test] 226 | fun test_sub() { 227 | assert!(sub(&from(123), &from(234)) == neg_from(111), 0); 228 | assert!(sub(&from(234), &from(123)) == from(111), 0); 229 | assert!(sub(&from(123), &neg_from(234)) == from(357), 0); 230 | assert!(sub(&neg_from(123), &from(234)) == neg_from(357), 0); 231 | assert!(sub(&neg_from(123), &neg_from(234)) == from(111), 0); 232 | assert!(sub(&neg_from(234), &neg_from(123)) == neg_from(111), 0); 233 | 234 | assert!(sub(&from(123), &from(123)) == zero(), 0); 235 | assert!(sub(&neg_from(123), &neg_from(123)) == zero(), 0); 236 | } 237 | 238 | #[test] 239 | fun test_mul() { 240 | assert!(mul(&from(123), &from(234)) == from(28782), 0); 241 | assert!(mul(&from(123), &neg_from(234)) == neg_from(28782), 0); 242 | assert!(mul(&neg_from(123), &from(234)) == neg_from(28782), 0); 243 | assert!(mul(&neg_from(123), &neg_from(234)) == from(28782), 0); 244 | } 245 | 246 | #[test] 247 | fun test_div() { 248 | assert!(div(&from(28781), &from(123)) == from(233), 0); 249 | assert!(div(&from(28781), &neg_from(123)) == neg_from(233), 0); 250 | assert!(div(&neg_from(28781), &from(123)) == neg_from(233), 0); 251 | assert!(div(&neg_from(28781), &neg_from(123)) == from(233), 0); 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /sui/sources/i64.move: -------------------------------------------------------------------------------- 1 | /// @title i64 2 | /// @notice Signed 64-bit integers in Move. 3 | /// @dev TODO: Pass in params by value instead of by ref to make usage easier? 4 | module movemate::i64 { 5 | use std::errors; 6 | 7 | /// @dev Maximum I64 value as a u64. 8 | const MAX_I64_AS_U64: u64 = (1 << 63) - 1; 9 | 10 | /// @dev u64 with the first bit set. An `I64` is negative if this bit is set. 11 | const U64_WITH_FIRST_BIT_SET: u64 = 1 << 63; 12 | 13 | /// When both `U256` equal. 14 | const EQUAL: u8 = 0; 15 | 16 | /// When `a` is less than `b`. 17 | const LESS_THAN: u8 = 1; 18 | 19 | /// When `b` is greater than `b`. 20 | const GREATER_THAN: u8 = 2; 21 | 22 | /// @dev When trying to convert from a u64 > MAX_I64_AS_U64 to an I64. 23 | const ECONVERSION_FROM_U64_OVERFLOW: u64 = 0; 24 | 25 | /// @dev When trying to convert from an negative I64 to a u64. 26 | const ECONVERSION_TO_U64_UNDERFLOW: u64 = 1; 27 | 28 | /// @notice Struct representing a signed 64-bit integer. 29 | struct I64 has copy, drop, store { 30 | bits: u64 31 | } 32 | 33 | /// @notice Casts a `u64` to an `I64`. 34 | public fun from(x: u64): I64 { 35 | assert!(x <= MAX_I64_AS_U64, errors::invalid_argument(ECONVERSION_FROM_U64_OVERFLOW)); 36 | I64 { bits: x } 37 | } 38 | 39 | /// @notice Creates a new `I64` with value 0. 40 | public fun zero(): I64 { 41 | I64 { bits: 0 } 42 | } 43 | 44 | /// @notice Casts an `I64` to a `u64`. 45 | public fun as_u64(x: &I64): u64 { 46 | assert!(x.bits < U64_WITH_FIRST_BIT_SET, errors::invalid_argument(ECONVERSION_TO_U64_UNDERFLOW)); 47 | x.bits 48 | } 49 | 50 | /// @notice Whether or not `x` is equal to 0. 51 | public fun is_zero(x: &I64): bool { 52 | x.bits == 0 53 | } 54 | 55 | /// @notice Whether or not `x` is negative. 56 | public fun is_neg(x: &I64): bool { 57 | x.bits > U64_WITH_FIRST_BIT_SET 58 | } 59 | 60 | /// @notice Flips the sign of `x`. 61 | public fun neg(x: &I64): I64 { 62 | if (x.bits == 0) return *x; 63 | I64 { bits: if (x.bits < U64_WITH_FIRST_BIT_SET) x.bits | (1 << 63) else x.bits - (1 << 63) } 64 | } 65 | 66 | /// @notice Flips the sign of `x`. 67 | public fun neg_from(x: u64): I64 { 68 | let ret = from(x); 69 | if (ret.bits > 0) *&mut ret.bits = ret.bits | (1 << 63); 70 | ret 71 | } 72 | 73 | /// @notice Absolute value of `x`. 74 | public fun abs(x: &I64): I64 { 75 | if (x.bits < U64_WITH_FIRST_BIT_SET) *x else I64 { bits: x.bits - (1 << 63) } 76 | } 77 | 78 | /// @notice Compare `a` and `b`. 79 | public fun compare(a: &I64, b: &I64): u8 { 80 | if (a.bits == b.bits) return EQUAL; 81 | if (a.bits < U64_WITH_FIRST_BIT_SET) { 82 | // A is positive 83 | if (b.bits < U64_WITH_FIRST_BIT_SET) { 84 | // B is positive 85 | return if (a.bits > b.bits) GREATER_THAN else LESS_THAN 86 | } else { 87 | // B is negative 88 | return GREATER_THAN 89 | } 90 | } else { 91 | // A is negative 92 | if (b.bits < U64_WITH_FIRST_BIT_SET) { 93 | // B is positive 94 | return LESS_THAN 95 | } else { 96 | // B is negative 97 | return if (a.bits > b.bits) LESS_THAN else GREATER_THAN 98 | } 99 | } 100 | } 101 | 102 | /// @notice Add `a + b`. 103 | public fun add(a: &I64, b: &I64): I64 { 104 | if (a.bits >> 63 == 0) { 105 | // A is positive 106 | if (b.bits >> 63 == 0) { 107 | // B is positive 108 | return I64 { bits: a.bits + b.bits } 109 | } else { 110 | // B is negative 111 | if (b.bits - (1 << 63) <= a.bits) return I64 { bits: a.bits - (b.bits - (1 << 63)) }; // Return positive 112 | return I64 { bits: b.bits - a.bits } // Return negative 113 | } 114 | } else { 115 | // A is negative 116 | if (b.bits >> 63 == 0) { 117 | // B is positive 118 | if (a.bits - (1 << 63) <= b.bits) return I64 { bits: b.bits - (a.bits - (1 << 63)) }; // Return positive 119 | return I64 { bits: a.bits - b.bits } // Return negative 120 | } else { 121 | // B is negative 122 | return I64 { bits: a.bits + (b.bits - (1 << 63)) } 123 | } 124 | } 125 | } 126 | 127 | /// @notice Subtract `a - b`. 128 | public fun sub(a: &I64, b: &I64): I64 { 129 | if (a.bits >> 63 == 0) { 130 | // A is positive 131 | if (b.bits >> 63 == 0) { 132 | // B is positive 133 | if (a.bits >= b.bits) return I64 { bits: a.bits - b.bits }; // Return positive 134 | return I64 { bits: (1 << 63) | (b.bits - a.bits) } // Return negative 135 | } else { 136 | // B is negative 137 | return I64 { bits: a.bits + (b.bits - (1 << 63)) } // Return negative 138 | } 139 | } else { 140 | // A is negative 141 | if (b.bits >> 63 == 0) { 142 | // B is positive 143 | return I64 { bits: a.bits + b.bits } // Return negative 144 | } else { 145 | // B is negative 146 | if (b.bits >= a.bits) return I64 { bits: b.bits - a.bits }; // Return positive 147 | return I64 { bits: a.bits - (b.bits - (1 << 63)) } // Return negative 148 | } 149 | } 150 | } 151 | 152 | /// @notice Multiply `a * b`. 153 | public fun mul(a: &I64, b: &I64): I64 { 154 | if (a.bits >> 63 == 0) { 155 | // A is positive 156 | if (b.bits >> 63 == 0) { 157 | // B is positive 158 | return I64 { bits: a.bits * b.bits } // Return positive 159 | } else { 160 | // B is negative 161 | return I64 { bits: (1 << 63) | (a.bits * (b.bits - (1 << 63))) } // Return negative 162 | } 163 | } else { 164 | // A is negative 165 | if (b.bits >> 63 == 0) { 166 | // B is positive 167 | return I64 { bits: (1 << 63) | (b.bits * (a.bits - (1 << 63))) } // Return negative 168 | } else { 169 | // B is negative 170 | return I64 { bits: (a.bits - (1 << 63)) * (b.bits - (1 << 63)) } // Return positive 171 | } 172 | } 173 | } 174 | 175 | /// @notice Divide `a / b`. 176 | public fun div(a: &I64, b: &I64): I64 { 177 | if (a.bits >> 63 == 0) { 178 | // A is positive 179 | if (b.bits >> 63 == 0) { 180 | // B is positive 181 | return I64 { bits: a.bits / b.bits } // Return positive 182 | } else { 183 | // B is negative 184 | return I64 { bits: (1 << 63) | (a.bits / (b.bits - (1 << 63))) } // Return negative 185 | } 186 | } else { 187 | // A is negative 188 | if (b.bits >> 63 == 0) { 189 | // B is positive 190 | return I64 { bits: (1 << 63) | ((a.bits - (1 << 63)) / b.bits) } // Return negative 191 | } else { 192 | // B is negative 193 | return I64 { bits: (a.bits - (1 << 63)) / (b.bits - (1 << 63)) } // Return positive 194 | } 195 | } 196 | } 197 | 198 | #[test] 199 | fun test_compare() { 200 | assert!(compare(&from(123), &from(123)) == EQUAL, 0); 201 | assert!(compare(&neg_from(123), &neg_from(123)) == EQUAL, 0); 202 | assert!(compare(&from(234), &from(123)) == GREATER_THAN, 0); 203 | assert!(compare(&from(123), &from(234)) == LESS_THAN, 0); 204 | assert!(compare(&neg_from(234), &neg_from(123)) == LESS_THAN, 0); 205 | assert!(compare(&neg_from(123), &neg_from(234)) == GREATER_THAN, 0); 206 | assert!(compare(&from(123), &neg_from(234)) == GREATER_THAN, 0); 207 | assert!(compare(&neg_from(123), &from(234)) == LESS_THAN, 0); 208 | assert!(compare(&from(234), &neg_from(123)) == GREATER_THAN, 0); 209 | assert!(compare(&neg_from(234), &from(123)) == LESS_THAN, 0); 210 | } 211 | 212 | #[test] 213 | fun test_add() { 214 | assert!(add(&from(123), &from(234)) == from(357), 0); 215 | assert!(add(&from(123), &neg_from(234)) == neg_from(111), 0); 216 | assert!(add(&from(234), &neg_from(123)) == from(111), 0); 217 | assert!(add(&neg_from(123), &from(234)) == from(111), 0); 218 | assert!(add(&neg_from(123), &neg_from(234)) == neg_from(357), 0); 219 | assert!(add(&neg_from(234), &neg_from(123)) == neg_from(357), 0); 220 | 221 | assert!(add(&from(123), &neg_from(123)) == zero(), 0); 222 | assert!(add(&neg_from(123), &from(123)) == zero(), 0); 223 | } 224 | 225 | #[test] 226 | fun test_sub() { 227 | assert!(sub(&from(123), &from(234)) == neg_from(111), 0); 228 | assert!(sub(&from(234), &from(123)) == from(111), 0); 229 | assert!(sub(&from(123), &neg_from(234)) == from(357), 0); 230 | assert!(sub(&neg_from(123), &from(234)) == neg_from(357), 0); 231 | assert!(sub(&neg_from(123), &neg_from(234)) == from(111), 0); 232 | assert!(sub(&neg_from(234), &neg_from(123)) == neg_from(111), 0); 233 | 234 | assert!(sub(&from(123), &from(123)) == zero(), 0); 235 | assert!(sub(&neg_from(123), &neg_from(123)) == zero(), 0); 236 | } 237 | 238 | #[test] 239 | fun test_mul() { 240 | assert!(mul(&from(123), &from(234)) == from(28782), 0); 241 | assert!(mul(&from(123), &neg_from(234)) == neg_from(28782), 0); 242 | assert!(mul(&neg_from(123), &from(234)) == neg_from(28782), 0); 243 | assert!(mul(&neg_from(123), &neg_from(234)) == from(28782), 0); 244 | } 245 | 246 | #[test] 247 | fun test_div() { 248 | assert!(div(&from(28781), &from(123)) == from(233), 0); 249 | assert!(div(&from(28781), &neg_from(123)) == neg_from(233), 0); 250 | assert!(div(&neg_from(28781), &from(123)) == neg_from(233), 0); 251 | assert!(div(&neg_from(28781), &neg_from(123)) == from(233), 0); 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /sui/sources/linear_vesting.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | /// @title linear_vesting 4 | /// @dev This contract handles the vesting of coins for a given beneficiary. Custody of multiple coins 5 | /// can be given to this contract, which will release the token to the beneficiary following a given vesting schedule. 6 | /// The vesting schedule is customizable through the {vestedAmount} function. 7 | /// Any token transferred to this contract will follow the vesting schedule as if they were locked from the beginning. 8 | /// Consequently, if the vesting has already started, any amount of tokens sent to this contract will (at least partly) 9 | /// be immediately releasable. 10 | module movemate::linear_vesting { 11 | use std::errors; 12 | use std::option::{Self, Option}; 13 | 14 | use sui::coin::{Self, Coin}; 15 | use sui::object::{Self, ID, Info}; 16 | use sui::transfer; 17 | use sui::tx_context::{Self, TxContext}; 18 | 19 | /// @dev When trying to clawback a wallet with the wrong wallet's capability. 20 | const EWRONG_CLAWBACK_CAPABILITY: u64 = 0; 21 | 22 | struct Wallet has key { 23 | info: Info, 24 | beneficiary: address, 25 | coin: Coin, 26 | released: u64, 27 | start: u64, 28 | duration: u64 29 | } 30 | 31 | struct ClawbackCapability has key, store { 32 | info: Info, 33 | wallet_id: ID 34 | } 35 | 36 | /// @dev Set the beneficiary, start timestamp and vesting duration of the vesting wallet. 37 | public entry fun init_wallet(beneficiary: address, start: u64, duration: u64, clawbacker: Option
, ctx: &mut TxContext) { 38 | let wallet = Wallet { 39 | info: object::new(ctx), 40 | beneficiary, 41 | coin: coin::zero(ctx), 42 | released: 0, 43 | start, 44 | duration 45 | }; 46 | if (option::is_some(&clawbacker)) transfer::transfer(ClawbackCapability { info: object::new(ctx), wallet_id: *object::id(&wallet) }, option::destroy_some(clawbacker)); 47 | transfer::share_object(wallet); 48 | } 49 | 50 | /// @dev Set the beneficiary, start timestamp and vesting duration of the vesting wallet. 51 | public fun init_wallet_return_clawback(beneficiary: address, start: u64, duration: u64, ctx: &mut TxContext): ClawbackCapability { 52 | let wallet = Wallet { 53 | info: object::new(ctx), 54 | beneficiary, 55 | coin: coin::zero(ctx), 56 | released: 0, 57 | start, 58 | duration 59 | }; 60 | let clawback_cap = ClawbackCapability { info: object::new(ctx), wallet_id: *object::id(&wallet) }; 61 | transfer::share_object(wallet); 62 | clawback_cap 63 | } 64 | 65 | /// @dev Deposits `coin_in` to `wallet`. 66 | public fun deposit(wallet: &mut Wallet, coin_in: Coin) { 67 | coin::join(&mut wallet.coin, coin_in) 68 | } 69 | 70 | /// @notice Returns the vesting wallet details. 71 | public fun wallet_info(wallet: &Wallet): (address, u64, u64, u64, u64) { 72 | (wallet.beneficiary, coin::value(&wallet.coin), wallet.released, wallet.start, wallet.duration) 73 | } 74 | 75 | /// @dev Release the tokens that have already vested. 76 | public entry fun release(wallet: &mut Wallet, ctx: &mut TxContext) { 77 | // Release amount 78 | let releasable = vested_amount(wallet.start, wallet.duration, coin::value(&wallet.coin), wallet.released, tx_context::epoch(ctx)) - wallet.released; 79 | *&mut wallet.released = *&wallet.released + releasable; 80 | coin::split_and_transfer(&mut wallet.coin, releasable, wallet.beneficiary, ctx); 81 | } 82 | 83 | /// @notice Claws back coins if enabled. 84 | /// @dev TODO: Possible to destroy shared wallet object? 85 | public fun clawback(wallet: &mut Wallet, clawback_cap: ClawbackCapability, ctx: &mut TxContext): Coin { 86 | // Check and delete clawback capability 87 | let ClawbackCapability { 88 | info: info, 89 | wallet_id: wallet_id 90 | } = clawback_cap; 91 | assert!(wallet_id == *object::id(wallet), errors::requires_capability(EWRONG_CLAWBACK_CAPABILITY)); 92 | object::delete(info); 93 | 94 | // Release amount 95 | let releasable = vested_amount(wallet.start, wallet.duration, coin::value(&wallet.coin), wallet.released, tx_context::epoch(ctx)) - wallet.released; 96 | *&mut wallet.released = *&wallet.released + releasable; 97 | coin::split_and_transfer(&mut wallet.coin, releasable, wallet.beneficiary, ctx); 98 | 99 | // Execute clawback 100 | let coin_out = &mut wallet.coin; 101 | let value = coin::value(coin_out); 102 | coin::take(coin::balance_mut(coin_out), value, ctx) 103 | } 104 | 105 | /// @notice Claws back coins to the `recipient` if enabled. 106 | public entry fun clawback_to(wallet: &mut Wallet, clawback_cap: ClawbackCapability, recipient: address, ctx: &mut TxContext) { 107 | coin::transfer(clawback(wallet, clawback_cap, ctx), recipient) 108 | } 109 | 110 | /// @dev Destroys a clawback capability. 111 | public fun destroy_clawback_capability(clawback_cap: ClawbackCapability) { 112 | let ClawbackCapability { 113 | info: info, 114 | wallet_id: _ 115 | } = clawback_cap; 116 | object::delete(info); 117 | } 118 | 119 | /// @dev Returns (1) the amount that has vested at the current time and the (2) portion of that amount that has not yet been released. 120 | public fun vesting_status(wallet: &Wallet, ctx: &mut TxContext): (u64, u64) { 121 | let vested = vested_amount(wallet.start, wallet.duration, coin::value(&wallet.coin), wallet.released, tx_context::epoch(ctx)); 122 | (vested, vested - wallet.released) 123 | } 124 | 125 | /// Calculates the amount that has already vested. Default implementation is a linear vesting curve. 126 | fun vested_amount(start: u64, duration: u64, balance: u64, already_released: u64, timestamp: u64): u64 { 127 | vesting_schedule(start, duration, balance + already_released, timestamp) 128 | } 129 | 130 | /// @dev Virtual implementation of the vesting formula. This returns the amount vested, as a function of time, for an asset given its total historical allocation. 131 | fun vesting_schedule(start: u64, duration: u64, total_allocation: u64, timestamp: u64): u64 { 132 | if (timestamp < start) return 0; 133 | if (timestamp > start + duration) return total_allocation; 134 | (total_allocation * (timestamp - start)) / duration 135 | } 136 | 137 | #[test_only] 138 | use sui::test_scenario; 139 | 140 | #[test_only] 141 | const TEST_ADMIN_ADDR: address = @0xA11CE; 142 | 143 | #[test_only] 144 | const TEST_BENEFICIARY_ADDR: address = @0xB0B; 145 | 146 | #[test_only] 147 | struct FakeMoney { } 148 | 149 | #[test] 150 | public entry fun test_end_to_end() { 151 | // Test scenario 152 | let scenario = &mut test_scenario::begin(&TEST_ADMIN_ADDR); 153 | 154 | // Mint fake coin 155 | let coin_in = coin::mint_for_testing(1234567890, test_scenario::ctx(scenario)); 156 | 157 | // init wallet and asset 158 | init_wallet(TEST_BENEFICIARY_ADDR, tx_context::epoch(test_scenario::ctx(scenario)), 7, option::some(TEST_ADMIN_ADDR), test_scenario::ctx(scenario)); 159 | test_scenario::next_tx(scenario, &TEST_ADMIN_ADDR); 160 | let wallet_wrapper = test_scenario::take_shared>(scenario); 161 | let wallet = test_scenario::borrow_mut(&mut wallet_wrapper); 162 | deposit(wallet, coin_in); 163 | 164 | // fast forward and release 165 | test_scenario::next_epoch(scenario); 166 | test_scenario::next_epoch(scenario); 167 | release(wallet, test_scenario::ctx(scenario)); 168 | test_scenario::return_shared(scenario, wallet_wrapper); 169 | 170 | // Ensure release worked as planned 171 | test_scenario::next_tx(scenario, &TEST_BENEFICIARY_ADDR); 172 | let beneficiary_coin = test_scenario::take_owned>(scenario); 173 | assert!(coin::value(&beneficiary_coin) == 352733682, 0); 174 | test_scenario::return_owned(scenario, beneficiary_coin); 175 | 176 | // fast forward and claw back vesting 177 | test_scenario::next_tx(scenario, &TEST_ADMIN_ADDR); 178 | test_scenario::next_epoch(scenario); 179 | test_scenario::next_epoch(scenario); 180 | test_scenario::next_epoch(scenario); 181 | let wallet_wrapper = test_scenario::take_shared>(scenario); 182 | let wallet = test_scenario::borrow_mut(&mut wallet_wrapper); 183 | let clawback_cap = test_scenario::take_owned(scenario); 184 | clawback_to(wallet, clawback_cap, TEST_ADMIN_ADDR, test_scenario::ctx(scenario)); 185 | test_scenario::return_shared(scenario, wallet_wrapper); 186 | 187 | // Ensure clawback worked as planned 188 | test_scenario::next_tx(scenario, &TEST_BENEFICIARY_ADDR); 189 | let beneficiary_coin = test_scenario::take_last_created_owned>(scenario); 190 | assert!(coin::value(&beneficiary_coin) == 881834207 - 352733682, 1); 191 | test_scenario::return_owned(scenario, beneficiary_coin); 192 | test_scenario::next_tx(scenario, &TEST_ADMIN_ADDR); 193 | let admin_coin = test_scenario::take_owned>(scenario); 194 | assert!(coin::value(&admin_coin) == 1234567890 - 881834207, 2); 195 | test_scenario::return_owned(scenario, admin_coin); 196 | } 197 | 198 | #[test] 199 | public entry fun test_no_clawback() { 200 | // Test scenario 201 | let scenario = &mut test_scenario::begin(&TEST_ADMIN_ADDR); 202 | 203 | // Mint fake coin 204 | let coin_in = coin::mint_for_testing(1234567890, test_scenario::ctx(scenario)); 205 | 206 | // init wallet and asset 207 | init_wallet(TEST_BENEFICIARY_ADDR, tx_context::epoch(test_scenario::ctx(scenario)), 7, option::none(), test_scenario::ctx(scenario)); 208 | test_scenario::next_tx(scenario, &TEST_ADMIN_ADDR); 209 | let wallet_wrapper = test_scenario::take_shared>(scenario); 210 | let wallet = test_scenario::borrow_mut(&mut wallet_wrapper); 211 | deposit(wallet, coin_in); 212 | 213 | // fast forward and claw back (should fail) 214 | test_scenario::next_epoch(scenario); 215 | test_scenario::next_epoch(scenario); 216 | assert!(!test_scenario::can_take_owned(scenario), 0); 217 | 218 | // clean up: return shared wallet object 219 | test_scenario::return_shared(scenario, wallet_wrapper); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /sui/sources/math.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | /// @title math 4 | /// @dev Standard math utilities missing in the Move language (for `u64`). 5 | module movemate::math { 6 | const ROUNDING_DOWN: u8 = 0; // Toward negative infinity 7 | const ROUNDING_UP: u8 = 0; // Toward infinity 8 | const ROUNDING_ZERO: u8 = 0; // Toward zero 9 | const SCALAR: u64 = 1 << 16; 10 | 11 | /// @dev Returns the largest of two numbers. 12 | public fun max(a: u64, b: u64): u64 { 13 | if (a >= b) a else b 14 | } 15 | 16 | /// @dev Returns the smallest of two numbers. 17 | public fun min(a: u64, b: u64): u64 { 18 | if (a < b) a else b 19 | } 20 | 21 | /// @dev Returns the average of two numbers. The result is rounded towards zero. 22 | public fun average(a: u64, b: u64): u64 { 23 | // (a + b) / 2 can overflow. 24 | (a & b) + (a ^ b) / 2 25 | } 26 | 27 | /// @dev Returns the ceiling of the division of two numbers. 28 | /// This differs from standard division with `/` in that it rounds up instead of rounding down. 29 | public fun ceil_div(a: u64, b: u64): u64 { 30 | // (a + b - 1) / b can overflow on addition, so we distribute. 31 | if (a == 0) 0 else (a - 1) / b + 1 32 | } 33 | 34 | /// @dev Returns a to the power of b. 35 | public fun exp(a: u64, b: u64): u64 { 36 | let c = 1; 37 | 38 | while (b > 0) { 39 | if (b & 1 > 0) c = c * a; 40 | b = b >> 1; 41 | a = a * a; 42 | }; 43 | 44 | c 45 | } 46 | 47 | /// @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down. 48 | /// Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). 49 | /// Costs only 9 gas in comparison to the 16 gas `sui::math::sqrt` costs (tested on Aptos). 50 | public fun sqrt(a: u64): u64 { 51 | if (a == 0) { 52 | return 0 53 | }; 54 | 55 | // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. 56 | // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have 57 | // `msb(a) <= a < 2*msb(a)`. 58 | // We also know that `k`, the position of the most significant bit, is such that `msb(a) = 2**k`. 59 | // This gives `2**k < a <= 2**(k+1)` => `2**(k/2) <= sqrt(a) < 2 ** (k/2+1)`. 60 | // Using an algorithm similar to the msb computation, we are able to compute `result = 2**(k/2)` which is a 61 | // good first approximation of `sqrt(a)` with at least 1 correct bit. 62 | let result = 1; 63 | let x = a; 64 | if (x >> 32 > 0) { 65 | x = x >> 32; 66 | result = result << 16; 67 | }; 68 | if (x >> 16 > 0) { 69 | x = x >> 16; 70 | result = result << 8; 71 | }; 72 | if (x >> 8 > 0) { 73 | x = x >> 8; 74 | result = result << 4; 75 | }; 76 | if (x >> 4 > 0) { 77 | x = x >> 4; 78 | result = result << 2; 79 | }; 80 | if (x >> 2 > 0) { 81 | result = result << 1; 82 | }; 83 | 84 | // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, 85 | // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at 86 | // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision 87 | // into the expected uint128 result. 88 | result = (result + a / result) >> 1; 89 | result = (result + a / result) >> 1; 90 | result = (result + a / result) >> 1; 91 | result = (result + a / result) >> 1; 92 | result = (result + a / result) >> 1; 93 | result = (result + a / result) >> 1; 94 | result = (result + a / result) >> 1; 95 | min(result, a / result) 96 | } 97 | 98 | /// @notice Calculates sqrt(a), following the selected rounding direction. 99 | public fun sqrt_rounding(a: u64, rounding: u8): u64 { 100 | let result = sqrt(a); 101 | if (rounding == ROUNDING_UP && result * result < a) { 102 | result = result + 1; 103 | }; 104 | result 105 | } 106 | 107 | /// @notice Calculates ax^2 + bx + c assuming all variables are scaled by 2**16. 108 | public fun quadratic(x: u64, a: u64, b: u64, c: u64): u64 { 109 | (exp(x, 2) / SCALAR * a / SCALAR) 110 | + (b * x / SCALAR) 111 | + c 112 | } 113 | 114 | #[test] 115 | fun test_exp() { 116 | assert!(exp(0, 0) == 1, 0); // TODO: Should this be undefined? 117 | assert!(exp(0, 1) == 0, 1); 118 | assert!(exp(0, 5) == 0, 2); 119 | 120 | assert!(exp(1, 0) == 1, 3); 121 | assert!(exp(1, 1) == 1, 4); 122 | assert!(exp(1, 5) == 1, 5); 123 | 124 | assert!(exp(2, 0) == 1, 6); 125 | assert!(exp(2, 1) == 2, 7); 126 | assert!(exp(2, 5) == 32, 8); 127 | 128 | assert!(exp(123, 0) == 1, 9); 129 | assert!(exp(123, 1) == 123, 10); 130 | assert!(exp(123, 5) == 28153056843, 11); 131 | 132 | assert!(exp(45, 6) == 8303765625, 12); 133 | } 134 | 135 | #[test] 136 | fun test_sqrt() { 137 | assert!(sqrt(0) == 0, 0); 138 | assert!(sqrt(1) == 1, 1); 139 | 140 | assert!(sqrt(2) == 1, 2); 141 | assert!(sqrt_rounding(2, ROUNDING_UP) == 2, 3); 142 | 143 | assert!(sqrt(169) == 13, 4); 144 | assert!(sqrt_rounding(169, ROUNDING_UP) == 13, 5); 145 | assert!(sqrt_rounding(170, ROUNDING_UP) == 14, 6); 146 | assert!(sqrt(195) == 13, 7); 147 | assert!(sqrt(196) == 14, 8); 148 | 149 | assert!(sqrt(55423988929) == 235423, 9); 150 | assert!(sqrt_rounding(55423988929, ROUNDING_UP) == 235423, 10); 151 | assert!(sqrt(55423988930) == 235423, 11); 152 | assert!(sqrt_rounding(55423988930, ROUNDING_UP) == 235424, 12); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /sui/sources/math_safe_precise.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | /// @title math_safe_precise 4 | /// @dev Overflow-avoidant and precise math utilities missing in the Move language (for `u64`). 5 | /// Specifically: 6 | /// 1) `mul_div` calculates `a * b / c` but converts to `u128`s during calculations to avoid overflow. 7 | /// 2) `quadratic` is the same as `math::quadratic` but uses `2**32` as a scalar instead of `2**16` for more precise math (converting to `u128`s during calculations for safety). 8 | module movemate::math_safe_precise { 9 | use movemate::math_u128; 10 | 11 | const SCALAR: u64 = 1 << 32; 12 | const SCALAR_U128: u128 = 1 << 32; 13 | 14 | /// @notice Calculates `a * b / c` but converts to `u128`s for calculations to avoid overflow. 15 | public fun mul_div(a: u64, b: u64, c: u64): u64 { 16 | ((a as u128) * (b as u128) / (c as u128) as u64) 17 | } 18 | 19 | /// @notice Calculates `ax^2 + bx + c` assuming all variables are scaled by `2**32`. 20 | public fun quadratic(x: u64, a: u64, b: u64, c: u64): u64 { 21 | ( 22 | (math_u128::exp((x as u128), 2) / SCALAR_U128 * (a as u128) / SCALAR_U128) 23 | + ((b as u128) * (x as u128) / SCALAR_U128) 24 | + (c as u128) 25 | as u64) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sui/sources/math_u128.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | /// @title math_u128 4 | /// @dev Standard math utilities missing in the Move language (for `u128`). 5 | module movemate::math_u128 { 6 | const ROUNDING_DOWN: u8 = 0; // Toward negative infinity 7 | const ROUNDING_UP: u8 = 0; // Toward infinity 8 | const ROUNDING_ZERO: u8 = 0; // Toward zero 9 | const SCALAR: u128 = 1 << 32; 10 | 11 | /// @dev Returns the largest of two numbers. 12 | public fun max(a: u128, b: u128): u128 { 13 | if (a >= b) a else b 14 | } 15 | 16 | /// @dev Returns the smallest of two numbers. 17 | public fun min(a: u128, b: u128): u128 { 18 | if (a < b) a else b 19 | } 20 | 21 | /// @dev Returns the average of two numbers. The result is rounded towards zero. 22 | public fun average(a: u128, b: u128): u128 { 23 | // (a + b) / 2 can overflow. 24 | (a & b) + (a ^ b) / 2 25 | } 26 | 27 | /// @dev Returns the ceiling of the division of two numbers. 28 | /// This differs from standard division with `/` in that it rounds up instead of rounding down. 29 | public fun ceil_div(a: u128, b: u128): u128 { 30 | // (a + b - 1) / b can overflow on addition, so we distribute. 31 | if (a == 0) 0 else (a - 1) / b + 1 32 | } 33 | 34 | /// @dev Returns a to the power of b. 35 | public fun exp(a: u128, b: u128): u128 { 36 | let c = 1; 37 | 38 | while (b > 0) { 39 | if (b & 1 > 0) c = c * a; 40 | b = b >> 1; 41 | a = a * a; 42 | }; 43 | 44 | c 45 | } 46 | 47 | /// @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down. 48 | /// Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). 49 | /// Costs only 9 gas in comparison to the 16 gas `sui::math::sqrt` costs (tested on Aptos). 50 | public fun sqrt(a: u128): u128 { 51 | if (a == 0) { 52 | return 0 53 | }; 54 | 55 | // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. 56 | // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have 57 | // `msb(a) <= a < 2*msb(a)`. 58 | // We also know that `k`, the position of the most significant bit, is such that `msb(a) = 2**k`. 59 | // This gives `2**k < a <= 2**(k+1)` => `2**(k/2) <= sqrt(a) < 2 ** (k/2+1)`. 60 | // Using an algorithm similar to the msb computation, we are able to compute `result = 2**(k/2)` which is a 61 | // good first approximation of `sqrt(a)` with at least 1 correct bit. 62 | let result = 1; 63 | let x = a; 64 | if (x >> 64 > 0) { 65 | x = x >> 64; 66 | result = result << 32; 67 | }; 68 | if (x >> 32 > 0) { 69 | x = x >> 32; 70 | result = result << 16; 71 | }; 72 | if (x >> 16 > 0) { 73 | x = x >> 16; 74 | result = result << 8; 75 | }; 76 | if (x >> 8 > 0) { 77 | x = x >> 8; 78 | result = result << 4; 79 | }; 80 | if (x >> 4 > 0) { 81 | x = x >> 4; 82 | result = result << 2; 83 | }; 84 | if (x >> 2 > 0) { 85 | result = result << 1; 86 | }; 87 | 88 | // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, 89 | // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at 90 | // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision 91 | // into the expected uint128 result. 92 | result = (result + a / result) >> 1; 93 | result = (result + a / result) >> 1; 94 | result = (result + a / result) >> 1; 95 | result = (result + a / result) >> 1; 96 | result = (result + a / result) >> 1; 97 | result = (result + a / result) >> 1; 98 | result = (result + a / result) >> 1; 99 | min(result, a / result) 100 | } 101 | 102 | /// @notice Calculates sqrt(a), following the selected rounding direction. 103 | public fun sqrt_rounding(a: u128, rounding: u8): u128 { 104 | let result = sqrt(a); 105 | if (rounding == ROUNDING_UP && result * result < a) { 106 | result = result + 1; 107 | }; 108 | result 109 | } 110 | 111 | /// @notice Calculates ax^2 + bx + c assuming all variables are scaled by 2**32. 112 | public fun quadratic(x: u128, a: u128, b: u128, c: u128): u128 { 113 | (exp(x, 2) / SCALAR * a / SCALAR) 114 | + (b * x / SCALAR) 115 | + c 116 | } 117 | 118 | #[test] 119 | fun test_exp() { 120 | assert!(exp(0, 0) == 1, 0); // TODO: Should this be undefined? 121 | assert!(exp(0, 1) == 0, 1); 122 | assert!(exp(0, 5) == 0, 2); 123 | 124 | assert!(exp(1, 0) == 1, 3); 125 | assert!(exp(1, 1) == 1, 4); 126 | assert!(exp(1, 5) == 1, 5); 127 | 128 | assert!(exp(2, 0) == 1, 6); 129 | assert!(exp(2, 1) == 2, 7); 130 | assert!(exp(2, 5) == 32, 8); 131 | 132 | assert!(exp(123, 0) == 1, 9); 133 | assert!(exp(123, 1) == 123, 10); 134 | assert!(exp(123, 5) == 28153056843, 11); 135 | 136 | assert!(exp(45, 6) == 8303765625, 12); 137 | } 138 | 139 | #[test] 140 | fun test_sqrt() { 141 | assert!(sqrt(0) == 0, 0); 142 | assert!(sqrt(1) == 1, 1); 143 | 144 | assert!(sqrt(2) == 1, 2); 145 | assert!(sqrt_rounding(2, ROUNDING_UP) == 2, 3); 146 | 147 | assert!(sqrt(169) == 13, 4); 148 | assert!(sqrt_rounding(169, ROUNDING_UP) == 13, 5); 149 | assert!(sqrt_rounding(170, ROUNDING_UP) == 14, 6); 150 | assert!(sqrt(195) == 13, 7); 151 | assert!(sqrt(196) == 14, 8); 152 | 153 | assert!(sqrt(55423988929) == 235423, 9); 154 | assert!(sqrt_rounding(55423988929, ROUNDING_UP) == 235423, 10); 155 | assert!(sqrt(55423988930) == 235423, 11); 156 | assert!(sqrt_rounding(55423988930, ROUNDING_UP) == 235424, 12); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /sui/sources/merkle_proof.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Based on: OpenZeppelin Contracts (last updated v4.7.0) (utils/cryptography/MerkleProof.sol) 3 | 4 | /// @title: merkle_proof 5 | /// @dev These functions deal with verification of Merkle Tree proofs. 6 | /// The proofs can be generated using the JavaScript library 7 | /// https://github.com/miguelmota/merkletreejs[merkletreejs]. 8 | /// Note: the hashing algorithm should be sha256 and pair sorting should be enabled. 9 | /// Example code below: 10 | /// const { MerkleTree } = require('merkletreejs') 11 | /// const SHA256 = require('crypto-js/sha256') 12 | /// const leaves = ['a', 'b', 'c'].map(x => SHA256(x)) 13 | /// const tree = new MerkleTree(leaves, SHA256, { sortPairs: true }) 14 | /// const root = tree.getRoot().toString('hex') 15 | /// const leaf = SHA256('a') 16 | /// const proof = tree.getProof(leaf) 17 | /// console.log(tree.verify(proof, leaf, root)) // true 18 | /// TODO: Unit tests for multi-proof verification. 19 | module movemate::merkle_proof { 20 | use std::errors; 21 | use std::hash; 22 | use std::vector; 23 | 24 | use movemate::vectors; 25 | 26 | /// @dev When an invalid multi-proof is supplied. Proof flags length must equal proof length + leaves length - 1. 27 | const EINVALID_MULTI_PROOF: u64 = 0; 28 | 29 | /// @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree 30 | /// defined by `root`. For this, a `proof` must be provided, containing 31 | /// sibling hashes on the branch from the leaf to the root of the tree. Each 32 | /// pair of leaves and each pair of pre-images are assumed to be sorted. 33 | public fun verify( 34 | proof: &vector>, 35 | root: vector, 36 | leaf: vector 37 | ): bool { 38 | process_proof(proof, leaf) == root 39 | } 40 | 41 | /// @dev Returns the rebuilt hash obtained by traversing a Merkle tree up 42 | /// from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt 43 | /// hash matches the root of the tree. When processing the proof, the pairs 44 | /// of leafs & pre-images are assumed to be sorted. 45 | fun process_proof(proof: &vector>, leaf: vector): vector { 46 | let computed_hash = leaf; 47 | let proof_length = vector::length(proof); 48 | let i = 0; 49 | 50 | while (i < proof_length) { 51 | computed_hash = hash_pair(computed_hash, *vector::borrow(proof, i)); 52 | i = i + 1; 53 | }; 54 | 55 | computed_hash 56 | } 57 | 58 | /// @dev Returns true if the `leaves` can be proved to be a part of a Merkle tree defined by 59 | /// `root`, according to `proof` and `proofFlags` as described in {processMultiProof}. 60 | public fun multi_proof_verify( 61 | proof: &vector>, 62 | proof_flags: &vector, 63 | root: vector, 64 | leaves: &vector> 65 | ): bool { 66 | process_multi_proof(proof, proof_flags, leaves) == root 67 | } 68 | 69 | /// @dev Returns the root of a tree reconstructed from `leaves` and the sibling nodes in `proof`, 70 | /// consuming from one or the other at each step according to the instructions given by 71 | /// `proofFlags`. 72 | fun process_multi_proof( 73 | proof: &vector>, 74 | proof_flags: &vector, 75 | leaves: &vector>, 76 | ): vector { 77 | // This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by 78 | // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the 79 | // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of 80 | // the merkle tree. 81 | let leaves_len = vector::length(leaves); 82 | let total_hashes = vector::length(proof_flags); 83 | 84 | // Check proof validity. 85 | assert!(leaves_len + vector::length(proof) - 1 == total_hashes, errors::invalid_argument(EINVALID_MULTI_PROOF)); 86 | 87 | // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using 88 | // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". 89 | let hashes = vector::empty>(); 90 | let leaf_pos = 0; 91 | let hash_pos = 0; 92 | let proof_pos = 0; 93 | // At each step, we compute the next hash using two values: 94 | // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we 95 | // get the next hash. 96 | // - depending on the flag, either another value for the "main queue" (merging branches) or an element from the 97 | // `proof` array. 98 | let i = 0; 99 | 100 | while (i < total_hashes) { 101 | let a = if (leaf_pos < leaves_len) { 102 | leaf_pos = leaf_pos + 1; 103 | *vector::borrow(leaves, leaf_pos) 104 | } else { 105 | hash_pos = hash_pos + 1; 106 | *vector::borrow(&hashes, hash_pos) 107 | }; 108 | 109 | let b = if (*vector::borrow(proof_flags, i)) { 110 | if (leaf_pos < leaves_len) { 111 | leaf_pos = leaf_pos + 1; 112 | *vector::borrow(leaves, leaf_pos) 113 | } else { 114 | hash_pos = hash_pos + 1; 115 | *vector::borrow(&hashes, hash_pos) 116 | } 117 | } else { 118 | proof_pos = proof_pos + 1; 119 | *vector::borrow(proof, proof_pos) 120 | }; 121 | 122 | vector::push_back(&mut hashes, hash_pair(a, b)); 123 | i = i + 1; 124 | }; 125 | 126 | if (total_hashes > 0) { 127 | *vector::borrow(&hashes, total_hashes - 1) 128 | } else if (leaves_len > 0) { 129 | *vector::borrow(leaves, 0) 130 | } else { 131 | *vector::borrow(proof, 0) 132 | } 133 | } 134 | 135 | fun hash_pair(a: vector, b: vector): vector { 136 | if (vectors::lt(&a, &b)) efficient_hash(a, b) else efficient_hash(b, a) 137 | } 138 | 139 | fun efficient_hash(a: vector, b: vector): vector { 140 | vector::append(&mut a, b); 141 | hash::sha2_256(a) 142 | } 143 | 144 | #[test] 145 | fun test_verify() { 146 | let proof = vector::empty>(); 147 | vector::push_back(&mut proof, x"3e23e8160039594a33894f6564e1b1348bbd7a0088d42c4acb73eeaed59c009d"); 148 | vector::push_back(&mut proof, x"2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6"); 149 | let root = x"aea2dd4249dcecf97ca6a1556db7f21ebd6a40bbec0243ca61b717146a08c347"; 150 | let leaf = x"ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"; 151 | assert!(verify(&proof, root, leaf), 0); 152 | } 153 | 154 | #[test] 155 | fun test_verify_bad_proof() { 156 | let proof = vector::empty>(); 157 | vector::push_back(&mut proof, x"3e23e8160039594a33894f6564e1b1349bbd7a0088d42c4acb73eeaed59c009d"); 158 | vector::push_back(&mut proof, x"2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6"); 159 | let root = x"aea2dd4249dcecf97ca6a1556db7f21ebd6a40bbec0243ca61b717146a08c347"; 160 | let leaf = x"ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"; 161 | assert!(!verify(&proof, root, leaf), 0); 162 | } 163 | 164 | #[test] 165 | fun test_verify_bad_root() { 166 | let proof = vector::empty>(); 167 | vector::push_back(&mut proof, x"3e23e8160039594a33894f6564e1b1348bbd7a0088d42c4acb73eeaed59c009d"); 168 | vector::push_back(&mut proof, x"2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6"); 169 | let root = x"aea9dd4249dcecf97ca6a1556db7f21ebd6a40bbec0243ca61b717146a08c347"; 170 | let leaf = x"ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"; 171 | assert!(!verify(&proof, root, leaf), 0); 172 | } 173 | 174 | #[test] 175 | fun test_verify_bad_leaf() { 176 | let proof = vector::empty>(); 177 | vector::push_back(&mut proof, x"3e23e8160039594a33894f6564e1b1348bbd7a0088d42c4acb73eeaed59c009d"); 178 | vector::push_back(&mut proof, x"2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6"); 179 | let root = x"aea2dd4249dcecf97ca6a1556db7f21ebd6a40bbec0243ca61b717146a08c347"; 180 | let leaf = x"ca978112ca1bbdc1fac231b39a23dc4da786eff8147c4e72b9807785afee48bb"; 181 | assert!(!verify(&proof, root, leaf), 0); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /sui/sources/pseudorandom.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Based on: https://github.com/starcoinorg/starcoin-framework-commons/blob/main/sources/PseudoRandom.move 3 | 4 | /// @title pseudorandom 5 | /// @notice A pseudo random module on-chain. 6 | /// @dev Warning: 7 | /// The random mechanism in smart contracts is different from 8 | /// that in traditional programming languages. The value generated 9 | /// by random is predictable to Miners, so it can only be used in 10 | /// simple scenarios where Miners have no incentive to cheat. If 11 | /// large amounts of money are involved, DO NOT USE THIS MODULE to 12 | /// generate random numbers; try a more secure way. 13 | module movemate::pseudorandom { 14 | use std::bcs; 15 | use std::errors; 16 | use std::hash; 17 | use std::vector; 18 | 19 | use sui::object::{Self, Info}; 20 | use sui::transfer; 21 | use sui::tx_context::{Self, TxContext}; 22 | 23 | use movemate::bcd; 24 | 25 | const ENOT_ROOT: u64 = 0; 26 | const EHIGH_ARG_GREATER_THAN_LOW_ARG: u64 = 1; 27 | 28 | /// Resource that wraps an integer counter. 29 | struct Counter has key { 30 | info: Info, 31 | value: u64 32 | } 33 | 34 | /// Share a `Counter` resource with value `i`. 35 | fun init(ctx: &mut TxContext) { 36 | // Create and share a Counter resource. This is a privileged operation that 37 | // can only be done inside the module that declares the `Counter` resource 38 | transfer::share_object(Counter { info: object::new(ctx), value: 0 }); 39 | } 40 | 41 | /// Increment the value of the supplied `Counter` resource. 42 | fun increment(counter: &mut Counter): u64 { 43 | let c_ref = &mut counter.value; 44 | *c_ref = *c_ref + 1; 45 | *c_ref 46 | } 47 | 48 | /// Acquire a seed using: the hash of the counter, epoch, sender address, and new object ID. 49 | fun seed(sender: &address, counter: &mut Counter, ctx: &mut TxContext): vector { 50 | let counter_val = increment(counter); 51 | let counter_bytes = bcs::to_bytes(&counter_val); 52 | 53 | let epoch: u64 = tx_context::epoch(ctx); 54 | let epoch_bytes: vector = bcs::to_bytes(&epoch); 55 | 56 | let sender_bytes: vector = bcs::to_bytes(sender); 57 | 58 | let uid = object::new(ctx); 59 | let object_id_bytes: vector = object::info_id_bytes(&uid); 60 | object::delete(uid); 61 | 62 | let info: vector = vector::empty(); 63 | vector::append(&mut info, counter_bytes); 64 | vector::append(&mut info, sender_bytes); 65 | vector::append(&mut info, epoch_bytes); 66 | vector::append(&mut info, object_id_bytes); 67 | 68 | let hash: vector = hash::sha3_256(info); 69 | hash 70 | } 71 | 72 | /// Acquire a seed using: the hash of the epoch, sender address, and new object ID. 73 | fun seed_no_counter(sender: &address, ctx: &mut TxContext): vector { 74 | let epoch: u64 = tx_context::epoch(ctx); 75 | let epoch_bytes: vector = bcs::to_bytes(&epoch); 76 | 77 | let sender_bytes: vector = bcs::to_bytes(sender); 78 | 79 | let uid = object::new(ctx); 80 | let object_id_bytes: vector = object::info_id_bytes(&uid); 81 | object::delete(uid); 82 | 83 | let info: vector = vector::empty(); 84 | vector::append(&mut info, sender_bytes); 85 | vector::append(&mut info, epoch_bytes); 86 | vector::append(&mut info, object_id_bytes); 87 | 88 | let hash: vector = hash::sha3_256(info); 89 | hash 90 | } 91 | 92 | /// Acquire a seed using: the hash of the counter, epoch, sender address, and new object ID. 93 | fun seed_no_address(counter: &mut Counter, ctx: &mut TxContext): vector { 94 | let counter_val = increment(counter); 95 | let counter_bytes = bcs::to_bytes(&counter_val); 96 | 97 | let epoch: u64 = tx_context::epoch(ctx); 98 | let epoch_bytes: vector = bcs::to_bytes(&epoch); 99 | 100 | let sender_bytes: vector = bcs::to_bytes(&tx_context::sender(ctx)); 101 | 102 | let uid = object::new(ctx); 103 | let object_id_bytes: vector = object::info_id_bytes(&uid); 104 | object::delete(uid); 105 | 106 | let info: vector = vector::empty(); 107 | vector::append(&mut info, counter_bytes); 108 | vector::append(&mut info, sender_bytes); 109 | vector::append(&mut info, epoch_bytes); 110 | vector::append(&mut info, object_id_bytes); 111 | 112 | let hash: vector = hash::sha3_256(info); 113 | hash 114 | } 115 | 116 | /// Acquire a seed using: the hash of the counter and sender address. 117 | fun seed_no_ctx(sender: &address, counter: &mut Counter): vector { 118 | let counter_val = increment(counter); 119 | let counter_bytes = bcs::to_bytes(&counter_val); 120 | 121 | let sender_bytes: vector = bcs::to_bytes(sender); 122 | 123 | let info: vector = vector::empty(); 124 | vector::append(&mut info, counter_bytes); 125 | vector::append(&mut info, sender_bytes); 126 | 127 | let hash: vector = hash::sha3_256(info); 128 | hash 129 | } 130 | 131 | /// Acquire a seed using: the hash of the counter. 132 | fun seed_with_counter(counter: &mut Counter): vector { 133 | let counter_val = increment(counter); 134 | let counter_bytes = bcs::to_bytes(&counter_val); 135 | 136 | let hash: vector = hash::sha3_256(counter_bytes); 137 | hash 138 | } 139 | 140 | /// Acquire a seed using: the hash of the epoch, sender address, and a new object ID. 141 | fun seed_with_ctx(ctx: &mut TxContext): vector { 142 | let epoch: u64 = tx_context::epoch(ctx); 143 | let epoch_bytes: vector = bcs::to_bytes(&epoch); 144 | 145 | let sender_bytes: vector = bcs::to_bytes(&tx_context::sender(ctx)); 146 | 147 | let uid = object::new(ctx); 148 | let object_id_bytes: vector = object::info_id_bytes(&uid); 149 | object::delete(uid); 150 | 151 | let info: vector = vector::empty(); 152 | vector::append(&mut info, sender_bytes); 153 | vector::append(&mut info, epoch_bytes); 154 | vector::append(&mut info, object_id_bytes); 155 | 156 | let hash: vector = hash::sha3_256(info); 157 | hash 158 | } 159 | 160 | /// Generate a random u128 161 | public fun rand_u128_with_seed(_seed: vector): u128 { 162 | bcd::bytes_to_u128(_seed) 163 | } 164 | 165 | /// Generate a random integer range in [low, high). 166 | public fun rand_u128_range_with_seed(_seed: vector, low: u128, high: u128): u128 { 167 | assert!(high > low, errors::invalid_argument(EHIGH_ARG_GREATER_THAN_LOW_ARG)); 168 | let value = rand_u128_with_seed(_seed); 169 | (value % (high - low)) + low 170 | } 171 | 172 | /// Generate a random u64 173 | public fun rand_u64_with_seed(_seed: vector): u64 { 174 | bcd::bytes_to_u64(_seed) 175 | } 176 | 177 | /// Generate a random integer range in [low, high). 178 | public fun rand_u64_range_with_seed(_seed: vector, low: u64, high: u64): u64 { 179 | assert!(high > low, errors::invalid_argument(EHIGH_ARG_GREATER_THAN_LOW_ARG)); 180 | let value = rand_u64_with_seed(_seed); 181 | (value % (high - low)) + low 182 | } 183 | 184 | public fun rand_u128(sender: &address, counter: &mut Counter, ctx: &mut TxContext): u128 { rand_u128_with_seed(seed(sender, counter, ctx)) } 185 | public fun rand_u128_range(sender: &address, counter: &mut Counter, low: u128, high: u128, ctx: &mut TxContext): u128 { rand_u128_range_with_seed(seed(sender, counter, ctx), low, high) } 186 | public fun rand_u64(sender: &address, counter: &mut Counter, ctx: &mut TxContext): u64 { rand_u64_with_seed(seed(sender, counter, ctx)) } 187 | public fun rand_u64_range(sender: &address, counter: &mut Counter, low: u64, high: u64, ctx: &mut TxContext): u64 { rand_u64_range_with_seed(seed(sender, counter, ctx), low, high) } 188 | 189 | public fun rand_u128_no_counter(sender: &address, ctx: &mut TxContext): u128 { rand_u128_with_seed(seed_no_counter(sender, ctx)) } 190 | public fun rand_u128_range_no_counter(sender: &address, low: u128, high: u128, ctx: &mut TxContext): u128 { rand_u128_range_with_seed(seed_no_counter(sender, ctx), low, high) } 191 | public fun rand_u64_no_counter(sender: &address, ctx: &mut TxContext): u64 { rand_u64_with_seed(seed_no_counter(sender, ctx)) } 192 | public fun rand_u64_range_no_counter(sender: &address, low: u64, high: u64, ctx: &mut TxContext): u64 { rand_u64_range_with_seed(seed_no_counter(sender, ctx), low, high) } 193 | 194 | public fun rand_u128_no_address(counter: &mut Counter, ctx: &mut TxContext): u128 { rand_u128_with_seed(seed_no_address(counter, ctx)) } 195 | public fun rand_u128_range_no_address(counter: &mut Counter, low: u128, high: u128, ctx: &mut TxContext): u128 { rand_u128_range_with_seed(seed_no_address(counter, ctx), low, high) } 196 | public fun rand_u64_no_address(counter: &mut Counter, ctx: &mut TxContext): u64 { rand_u64_with_seed(seed_no_address(counter, ctx)) } 197 | public fun rand_u64_range_no_address(counter: &mut Counter, low: u64, high: u64, ctx: &mut TxContext): u64 { rand_u64_range_with_seed(seed_no_address(counter, ctx), low, high) } 198 | 199 | public fun rand_u128_no_ctx(sender: &address, counter: &mut Counter): u128 { rand_u128_with_seed(seed_no_ctx(sender, counter)) } 200 | public fun rand_u128_range_no_ctx(sender: &address, counter: &mut Counter, low: u128, high: u128): u128 { rand_u128_range_with_seed(seed_no_ctx(sender, counter), low, high) } 201 | public fun rand_u64_no_ctx(sender: &address, counter: &mut Counter): u64 { rand_u64_with_seed(seed_no_ctx(sender, counter)) } 202 | public fun rand_u64_range_no_ctx(sender: &address, counter: &mut Counter, low: u64, high: u64): u64 { rand_u64_range_with_seed(seed_no_ctx(sender, counter), low, high) } 203 | 204 | public fun rand_u128_with_counter(counter: &mut Counter): u128 { rand_u128_with_seed(seed_with_counter(counter)) } 205 | public fun rand_u128_range_with_counter(counter: &mut Counter, low: u128, high: u128): u128 { rand_u128_range_with_seed(seed_with_counter(counter), low, high) } 206 | public fun rand_u64_with_counter(counter: &mut Counter): u64 { rand_u64_with_seed(seed_with_counter(counter)) } 207 | public fun rand_u64_range_with_counter(counter: &mut Counter, low: u64, high: u64): u64 { rand_u64_range_with_seed(seed_with_counter(counter), low, high) } 208 | 209 | public fun rand_u128_with_ctx(ctx: &mut TxContext): u128 { rand_u128_with_seed(seed_with_ctx(ctx)) } 210 | public fun rand_u128_range_with_ctx(low: u128, high: u128, ctx: &mut TxContext): u128 { rand_u128_range_with_seed(seed_with_ctx(ctx), low, high) } 211 | public fun rand_u64_with_ctx(ctx: &mut TxContext): u64 { rand_u64_with_seed(seed_with_ctx(ctx)) } 212 | public fun rand_u64_range_with_ctx(low: u64, high: u64 ,ctx: &mut TxContext): u64 { rand_u64_range_with_seed(seed_with_ctx(ctx), low, high) } 213 | } 214 | -------------------------------------------------------------------------------- /sui/sources/quadratic_vesting.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | /// @title quadratic_vesting 4 | /// @dev This contract handles the vesting of coins for a given beneficiary. Custody of multiple coins 5 | /// can be given to this contract, which will release the token to the beneficiary following a given vesting schedule. 6 | /// The vesting schedule is customizable through the {vestedAmount} function. 7 | /// Any token transferred to this contract will follow the vesting schedule as if they were locked from the beginning. 8 | /// Consequently, if the vesting has already started, any amount of tokens sent to this contract will (at least partly) 9 | /// be immediately releasable. 10 | module movemate::quadratic_vesting { 11 | use std::errors; 12 | use std::option::{Self, Option}; 13 | 14 | use sui::coin::{Self, Coin}; 15 | use sui::object::{Self, ID, Info}; 16 | use sui::transfer; 17 | use sui::tx_context::{Self, TxContext}; 18 | 19 | use movemate::math; 20 | 21 | const SCALAR: u64 = 1 << 16; 22 | 23 | /// @dev When trying to clawback a wallet with the wrong wallet's capability. 24 | const EWRONG_CLAWBACK_CAPABILITY: u64 = 0; 25 | 26 | struct Wallet has key { 27 | info: Info, 28 | beneficiary: address, 29 | coin: Coin, 30 | released: u64, 31 | vesting_curve_a: u64, 32 | vesting_curve_b: u64, 33 | vesting_curve_c: u64, 34 | start: u64, 35 | cliff: u64, 36 | duration: u64 37 | } 38 | 39 | struct ClawbackCapability has key, store { 40 | info: Info, 41 | wallet_id: ID 42 | } 43 | 44 | /// @dev Set the beneficiary, start timestamp and vesting duration of the vesting wallet. 45 | public entry fun init_wallet( 46 | beneficiary: address, 47 | curve_a: u64, 48 | curve_b: u64, 49 | curve_c: u64, 50 | start: u64, 51 | cliff: u64, 52 | duration: u64, 53 | clawbacker: Option
, 54 | ctx: &mut TxContext 55 | ) { 56 | let wallet = Wallet { 57 | info: object::new(ctx), 58 | beneficiary, 59 | coin: coin::zero(ctx), 60 | released: 0, 61 | vesting_curve_a: curve_a, 62 | vesting_curve_b: curve_b, 63 | vesting_curve_c: curve_c, 64 | start, 65 | cliff, 66 | duration 67 | }; 68 | if (option::is_some(&clawbacker)) transfer::transfer(ClawbackCapability { info: object::new(ctx), wallet_id: *object::id(&wallet) }, option::destroy_some(clawbacker)); 69 | transfer::share_object(wallet); 70 | } 71 | 72 | /// @dev Set the beneficiary, start timestamp and vesting duration of the vesting wallet. 73 | public fun init_wallet_return_clawback( 74 | beneficiary: address, 75 | curve_a: u64, 76 | curve_b: u64, 77 | curve_c: u64, 78 | start: u64, 79 | cliff: u64, 80 | duration: u64, 81 | ctx: &mut TxContext 82 | ): ClawbackCapability { 83 | let wallet = Wallet { 84 | info: object::new(ctx), 85 | beneficiary, 86 | coin: coin::zero(ctx), 87 | released: 0, 88 | vesting_curve_a: curve_a, 89 | vesting_curve_b: curve_b, 90 | vesting_curve_c: curve_c, 91 | start, 92 | cliff, 93 | duration 94 | }; 95 | let clawback_cap = ClawbackCapability { info: object::new(ctx), wallet_id: *object::id(&wallet) }; 96 | transfer::share_object(wallet); 97 | clawback_cap 98 | } 99 | 100 | /// @dev Deposits `coin_in` to `wallet`. 101 | public fun deposit(wallet: &mut Wallet, coin_in: Coin) { 102 | coin::join(&mut wallet.coin, coin_in) 103 | } 104 | 105 | /// @notice Returns the vesting wallet details. 106 | public fun wallet_info(wallet: &mut Wallet): (address, u64, u64, u64, u64, u64, u64, u64, u64) { 107 | (wallet.beneficiary, coin::value(&wallet.coin), wallet.released, wallet.vesting_curve_a, wallet.vesting_curve_b, wallet.vesting_curve_c, wallet.start, wallet.cliff, wallet.duration) 108 | } 109 | 110 | /// @dev Release the tokens that have already vested. 111 | public entry fun release(wallet: &mut Wallet, ctx: &mut TxContext) { 112 | // Release amount 113 | let releasable = vested_amount(wallet.vesting_curve_a, wallet.vesting_curve_b, wallet.vesting_curve_c, wallet.start, wallet.cliff, wallet.duration, coin::value(&wallet.coin), wallet.released, tx_context::epoch(ctx)) - wallet.released; 114 | *&mut wallet.released = *&wallet.released + releasable; 115 | coin::split_and_transfer(&mut wallet.coin, releasable, wallet.beneficiary, ctx); 116 | } 117 | 118 | /// @notice Claws back coins to the `clawbacker` if enabled. 119 | /// @dev TODO: Possible to destroy shared wallet object? 120 | public fun clawback(wallet: &mut Wallet, clawback_cap: ClawbackCapability, ctx: &mut TxContext): Coin { 121 | // Check and delete clawback capability 122 | let ClawbackCapability { 123 | info: info, 124 | wallet_id: wallet_id 125 | } = clawback_cap; 126 | assert!(wallet_id == *object::id(wallet), errors::requires_capability(EWRONG_CLAWBACK_CAPABILITY)); 127 | object::delete(info); 128 | 129 | // Release amount 130 | let releasable = vested_amount(wallet.vesting_curve_a, wallet.vesting_curve_b, wallet.vesting_curve_c, wallet.start, wallet.cliff, wallet.duration, coin::value(&wallet.coin), wallet.released, tx_context::epoch(ctx)) - wallet.released; 131 | *&mut wallet.released = *&wallet.released + releasable; 132 | coin::split_and_transfer(&mut wallet.coin, releasable, wallet.beneficiary, ctx); 133 | 134 | // Execute clawback 135 | let coin_out = &mut wallet.coin; 136 | let value = coin::value(coin_out); 137 | coin::take(coin::balance_mut(coin_out), value, ctx) 138 | } 139 | 140 | /// @notice Claws back coins to the `recipient` if enabled. 141 | public entry fun clawback_to(wallet: &mut Wallet, clawback_cap: ClawbackCapability, recipient: address, ctx: &mut TxContext) { 142 | coin::transfer(clawback(wallet, clawback_cap, ctx), recipient) 143 | } 144 | 145 | /// @dev Destroys a clawback capability. 146 | public fun destroy_clawback_capability(clawback_cap: ClawbackCapability) { 147 | let ClawbackCapability { 148 | info: info, 149 | wallet_id: _ 150 | } = clawback_cap; 151 | object::delete(info); 152 | } 153 | 154 | /// @dev Returns (1) the amount that has vested at the current time and the (2) portion of that amount that has not yet been released. 155 | public fun vesting_status(wallet: &Wallet, ctx: &mut TxContext): (u64, u64) { 156 | let vested = vested_amount(wallet.vesting_curve_a, wallet.vesting_curve_b, wallet.vesting_curve_c, wallet.start, wallet.cliff, wallet.duration, coin::value(&wallet.coin), wallet.released, tx_context::epoch(ctx)); 157 | (vested, vested - wallet.released) 158 | } 159 | 160 | /// @dev Calculates the amount that has already vested. Default implementation is a linear vesting curve. 161 | fun vested_amount(a: u64, b: u64, c: u64, start: u64, cliff: u64, duration: u64, balance: u64, already_released: u64, timestamp: u64): u64 { 162 | vesting_schedule(a, b, c, start, cliff, duration, balance + already_released, timestamp) 163 | } 164 | 165 | /// @dev Virtual implementation of the vesting formula. This returns the amount vested, as a function of time, for an asset given its total historical allocation. 166 | fun vesting_schedule(a: u64, b: u64, c: u64, start: u64, cliff: u64, duration: u64, total_allocation: u64, timestamp: u64): u64 { 167 | // Get time delta, check domain, and convert to proportion out of SCALAR 168 | let time_delta = timestamp - start; 169 | if (time_delta < cliff) return 0; 170 | if (time_delta >= duration) return total_allocation; 171 | let progress = time_delta * SCALAR / duration; 172 | 173 | // Evaluate quadratic trinomial where y = vested proportion of total_allocation out of SCALAR and x = progress through vesting period out of SCALAR 174 | // No need to check for overflow when casting uint256 to int256 because `progress` maxes out at SCALAR and so does `(progress ** 2) / SCALAR` 175 | let vested_proportion = math::quadratic(progress, a, b, c); 176 | 177 | // Keep vested total_allocation in range [0, total] 178 | if (vested_proportion <= 0) return 0; 179 | if (vested_proportion >= SCALAR) return total_allocation; 180 | 181 | // Releasable = total_allocation * vested proportion (divided by SCALAR since proportion is scaled by SCALAR) 182 | total_allocation * vested_proportion / SCALAR 183 | } 184 | 185 | #[test_only] 186 | use sui::test_scenario; 187 | 188 | #[test_only] 189 | const TEST_ADMIN_ADDR: address = @0xA11CE; 190 | 191 | #[test_only] 192 | const TEST_BENEFICIARY_ADDR: address = @0xB0B; 193 | 194 | #[test_only] 195 | struct FakeMoney { } 196 | 197 | #[test] 198 | public entry fun test_end_to_end() { 199 | // Test scenario 200 | let scenario = &mut test_scenario::begin(&TEST_ADMIN_ADDR); 201 | 202 | // Mint fake coin 203 | let coin_in = coin::mint_for_testing(1234567890, test_scenario::ctx(scenario)); 204 | 205 | // init wallet and asset 206 | init_wallet(TEST_BENEFICIARY_ADDR, 0, SCALAR, 0, tx_context::epoch(test_scenario::ctx(scenario)), 0, 7, option::some(TEST_ADMIN_ADDR), test_scenario::ctx(scenario)); 207 | test_scenario::next_tx(scenario, &TEST_ADMIN_ADDR); 208 | let wallet_wrapper = test_scenario::take_shared>(scenario); 209 | let wallet = test_scenario::borrow_mut(&mut wallet_wrapper); 210 | deposit(wallet, coin_in); 211 | 212 | // fast forward and release 213 | test_scenario::next_epoch(scenario); 214 | test_scenario::next_epoch(scenario); 215 | release(wallet, test_scenario::ctx(scenario)); 216 | test_scenario::return_shared(scenario, wallet_wrapper); 217 | 218 | // Ensure release worked as planned 219 | test_scenario::next_tx(scenario, &TEST_BENEFICIARY_ADDR); 220 | let beneficiary_coin = test_scenario::take_owned>(scenario); 221 | assert!(coin::value(&beneficiary_coin) == 352722918, 0); 222 | test_scenario::return_owned(scenario, beneficiary_coin); 223 | 224 | // fast forward and claw back vesting 225 | test_scenario::next_tx(scenario, &TEST_ADMIN_ADDR); 226 | test_scenario::next_epoch(scenario); 227 | test_scenario::next_epoch(scenario); 228 | test_scenario::next_epoch(scenario); 229 | let wallet_wrapper = test_scenario::take_shared>(scenario); 230 | let wallet = test_scenario::borrow_mut(&mut wallet_wrapper); 231 | let clawback_cap = test_scenario::take_owned(scenario); 232 | clawback_to(wallet, clawback_cap, TEST_ADMIN_ADDR, test_scenario::ctx(scenario)); 233 | test_scenario::return_shared(scenario, wallet_wrapper); 234 | 235 | // Ensure clawback worked as planned 236 | test_scenario::next_tx(scenario, &TEST_BENEFICIARY_ADDR); 237 | let beneficiary_coin = test_scenario::take_last_created_owned>(scenario); 238 | assert!(coin::value(&beneficiary_coin) == 881826133 - 352722918, 1); 239 | test_scenario::return_owned(scenario, beneficiary_coin); 240 | test_scenario::next_tx(scenario, &TEST_ADMIN_ADDR); 241 | let admin_coin = test_scenario::take_owned>(scenario); 242 | assert!(coin::value(&admin_coin) == 1234567890 - 881826133, 2); 243 | test_scenario::return_owned(scenario, admin_coin); 244 | } 245 | 246 | #[test] 247 | public entry fun test_no_clawback() { 248 | // Test scenario 249 | let scenario = &mut test_scenario::begin(&TEST_ADMIN_ADDR); 250 | 251 | // Mint fake coin 252 | let coin_in = coin::mint_for_testing(1234567890, test_scenario::ctx(scenario)); 253 | 254 | // init wallet and asset 255 | init_wallet(TEST_BENEFICIARY_ADDR, 0, SCALAR, 0, tx_context::epoch(test_scenario::ctx(scenario)), 0, 7, option::none(), test_scenario::ctx(scenario)); 256 | test_scenario::next_tx(scenario, &TEST_ADMIN_ADDR); 257 | let wallet_wrapper = test_scenario::take_shared>(scenario); 258 | let wallet = test_scenario::borrow_mut(&mut wallet_wrapper); 259 | deposit(wallet, coin_in); 260 | 261 | // fast forward and claw back (should fail) 262 | test_scenario::next_epoch(scenario); 263 | test_scenario::next_epoch(scenario); 264 | assert!(!test_scenario::can_take_owned(scenario), 0); 265 | 266 | // clean up: return shared wallet object 267 | test_scenario::return_shared(scenario, wallet_wrapper); 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /sui/sources/to_string.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Source: https://github.com/starcoinorg/starcoin-framework-commons/blob/main/sources/StringUtil.move 3 | 4 | /// @title to_string 5 | /// @notice `u128` to `String` conversion utilities. 6 | module movemate::to_string { 7 | use std::ascii::{Self, String}; 8 | use std::vector; 9 | 10 | const HEX_SYMBOLS: vector = b"0123456789abcdef"; 11 | 12 | // Maximum value of u128, i.e. 2 ** 128 - 1 13 | // Source: https://github.com/move-language/move/blob/a86f31415b9a18867b5edaed6f915a39b8c2ef40/language/move-prover/doc/user/spec-lang.md?plain=1#L214 14 | const MAX_U128: u128 = 340282366920938463463374607431768211455; 15 | 16 | /// @dev Converts a `u128` to its `ascii::String` decimal representation. 17 | public fun to_string(value: u128): String { 18 | if (value == 0) { 19 | return ascii::string(b"0") 20 | }; 21 | let buffer = vector::empty(); 22 | while (value != 0) { 23 | vector::push_back(&mut buffer, ((48 + value % 10) as u8)); 24 | value = value / 10; 25 | }; 26 | vector::reverse(&mut buffer); 27 | ascii::string(buffer) 28 | } 29 | 30 | /// @dev Converts a `u128` to its `ascii::String` hexadecimal representation. 31 | public fun to_hex_string(value: u128): String { 32 | if (value == 0) { 33 | return ascii::string(b"0x00") 34 | }; 35 | let temp: u128 = value; 36 | let length: u128 = 0; 37 | while (temp != 0) { 38 | length = length + 1; 39 | temp = temp >> 8; 40 | }; 41 | to_hex_string_fixed_length(value, length) 42 | } 43 | 44 | /// @dev Converts a `u128` to its `ascii::String` hexadecimal representation with fixed length (in whole bytes). 45 | /// so the returned String is `2 * length + 2`(with '0x') in size 46 | public fun to_hex_string_fixed_length(value: u128, length: u128): String { 47 | let buffer = vector::empty(); 48 | 49 | let i: u128 = 0; 50 | while (i < length * 2) { 51 | vector::push_back(&mut buffer, *vector::borrow(&mut HEX_SYMBOLS, (value & 0xf as u64))); 52 | value = value >> 4; 53 | i = i + 1; 54 | }; 55 | assert!(value == 0, 1); 56 | vector::append(&mut buffer, b"x0"); 57 | vector::reverse(&mut buffer); 58 | ascii::string(buffer) 59 | } 60 | 61 | /// @dev Converts a `vector` to its `ascii::String` hexadecimal representation. 62 | /// so the returned String is `2 * length + 2`(with '0x') in size 63 | public fun bytes_to_hex_string(bytes: &vector): String { 64 | let length = vector::length(bytes); 65 | let buffer = b"0x"; 66 | 67 | let i: u64 = 0; 68 | while (i < length) { 69 | let byte = *vector::borrow(bytes, i); 70 | vector::push_back(&mut buffer, *vector::borrow(&mut HEX_SYMBOLS, (byte >> 4 & 0xf as u64))); 71 | vector::push_back(&mut buffer, *vector::borrow(&mut HEX_SYMBOLS, (byte & 0xf as u64))); 72 | i = i + 1; 73 | }; 74 | ascii::string(buffer) 75 | } 76 | 77 | #[test] 78 | fun test_to_string() { 79 | assert!(b"0" == ascii::into_bytes(to_string(0)), 1); 80 | assert!(b"1" == ascii::into_bytes(to_string(1)), 1); 81 | assert!(b"257" == ascii::into_bytes(to_string(257)), 1); 82 | assert!(b"10" == ascii::into_bytes(to_string(10)), 1); 83 | assert!(b"12345678" == ascii::into_bytes(to_string(12345678)), 1); 84 | assert!(b"340282366920938463463374607431768211455" == ascii::into_bytes(to_string(MAX_U128)), 1); 85 | } 86 | 87 | #[test] 88 | fun test_to_hex_string() { 89 | assert!(b"0x00" == ascii::into_bytes(to_hex_string(0)), 1); 90 | assert!(b"0x01" == ascii::into_bytes(to_hex_string(1)), 1); 91 | assert!(b"0x0101" == ascii::into_bytes(to_hex_string(257)), 1); 92 | assert!(b"0xbc614e" == ascii::into_bytes(to_hex_string(12345678)), 1); 93 | assert!(b"0xffffffffffffffffffffffffffffffff" == ascii::into_bytes(to_hex_string(MAX_U128)), 1); 94 | } 95 | 96 | #[test] 97 | fun test_to_hex_string_fixed_length() { 98 | assert!(b"0x00" == ascii::into_bytes(to_hex_string_fixed_length(0, 1)), 1); 99 | assert!(b"0x01" == ascii::into_bytes(to_hex_string_fixed_length(1, 1)), 1); 100 | assert!(b"0x10" == ascii::into_bytes(to_hex_string_fixed_length(16, 1)), 1); 101 | assert!(b"0x0011" == ascii::into_bytes(to_hex_string_fixed_length(17, 2)), 1); 102 | assert!(b"0x0000bc614e" == ascii::into_bytes(to_hex_string_fixed_length(12345678, 5)), 1); 103 | assert!(b"0xffffffffffffffffffffffffffffffff" == ascii::into_bytes(to_hex_string_fixed_length(MAX_U128, 16)), 1); 104 | } 105 | 106 | #[test] 107 | fun test_bytes_to_hex_string() { 108 | assert!(b"0x00" == ascii::into_bytes(bytes_to_hex_string(&x"00")), 1); 109 | assert!(b"0x01" == ascii::into_bytes(bytes_to_hex_string(&x"01")), 1); 110 | assert!(b"0x1924bacf" == ascii::into_bytes(bytes_to_hex_string(&x"1924bacf")), 1); 111 | assert!(b"0x8324445443539749823794832789472398748932794327743277489327498732" == ascii::into_bytes(bytes_to_hex_string(&x"8324445443539749823794832789472398748932794327743277489327498732")), 1); 112 | assert!(b"0xbfee823235227564" == ascii::into_bytes(bytes_to_hex_string(&x"bfee823235227564")), 1); 113 | assert!(b"0xffffffffffffffffffffffffffffffff" == ascii::into_bytes(bytes_to_hex_string(&x"ffffffffffffffffffffffffffffffff")), 1); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /sui/sources/vectors.move: -------------------------------------------------------------------------------- 1 | /// @title vectors 2 | /// @notice Vector utilities. 3 | /// @dev TODO: Fuzz testing? 4 | module movemate::vectors { 5 | use std::errors; 6 | use std::vector; 7 | 8 | use movemate::math; 9 | 10 | /// @dev When you supply vectors of different lengths to a function requiring equal-length vectors. 11 | /// TODO: Support variable length vectors? 12 | const EVECTOR_LENGTH_MISMATCH: u64 = 0; 13 | 14 | /// @dev Searches a sorted `vec` and returns the first index that contains 15 | /// a value greater or equal to `element`. If no such index exists (i.e. all 16 | /// values in the vector are strictly less than `element`), the vector length is 17 | /// returned. Time complexity O(log n). 18 | /// `vec` is expected to be sorted in ascending order, and to contain no 19 | /// repeated elements. 20 | public fun find_upper_bound(vec: &vector, element: u64): u64 { 21 | if (vector::length(vec) == 0) { 22 | return 0 23 | }; 24 | 25 | let low = 0; 26 | let high = vector::length(vec); 27 | 28 | while (low < high) { 29 | let mid = math::average(low, high); 30 | 31 | // Note that mid will always be strictly less than high (i.e. it will be a valid vector index) 32 | // because Math::average rounds down (it does integer division with truncation). 33 | if (*vector::borrow(vec, mid) > element) { 34 | high = mid; 35 | } else { 36 | low = mid + 1; 37 | } 38 | }; 39 | 40 | // At this point `low` is the exclusive upper bound. We will return the inclusive upper bound. 41 | if (low > 0 && *vector::borrow(vec, low - 1) == element) { 42 | low - 1 43 | } else { 44 | low 45 | } 46 | } 47 | 48 | public fun lt(a: &vector, b: &vector): bool { 49 | let i = 0; 50 | let len = vector::length(a); 51 | assert!(len == vector::length(b), errors::invalid_argument(EVECTOR_LENGTH_MISMATCH)); 52 | 53 | while (i < len) { 54 | let aa = *vector::borrow(a, i); 55 | let bb = *vector::borrow(b, i); 56 | if (aa < bb) return true; 57 | if (aa > bb) return false; 58 | i = i + 1; 59 | }; 60 | 61 | false 62 | } 63 | 64 | public fun gt(a: &vector, b: &vector): bool { 65 | let i = 0; 66 | let len = vector::length(a); 67 | assert!(len == vector::length(b), errors::invalid_argument(EVECTOR_LENGTH_MISMATCH)); 68 | 69 | while (i < len) { 70 | let aa = *vector::borrow(a, i); 71 | let bb = *vector::borrow(b, i); 72 | if (aa > bb) return true; 73 | if (aa < bb) return false; 74 | i = i + 1; 75 | }; 76 | 77 | false 78 | } 79 | 80 | public fun lte(a: &vector, b: &vector): bool { 81 | let i = 0; 82 | let len = vector::length(a); 83 | assert!(len == vector::length(b), errors::invalid_argument(EVECTOR_LENGTH_MISMATCH)); 84 | 85 | while (i < len) { 86 | let aa = *vector::borrow(a, i); 87 | let bb = *vector::borrow(b, i); 88 | if (aa < bb) return true; 89 | if (aa > bb) return false; 90 | i = i + 1; 91 | }; 92 | 93 | true 94 | } 95 | 96 | public fun gte(a: &vector, b: &vector): bool { 97 | let i = 0; 98 | let len = vector::length(a); 99 | assert!(len == vector::length(b), errors::invalid_argument(EVECTOR_LENGTH_MISMATCH)); 100 | 101 | while (i < len) { 102 | let aa = *vector::borrow(a, i); 103 | let bb = *vector::borrow(b, i); 104 | if (aa > bb) return true; 105 | if (aa < bb) return false; 106 | i = i + 1; 107 | }; 108 | 109 | true 110 | } 111 | 112 | #[test] 113 | fun test_find_upper_bound() { 114 | let vec = vector::empty(); 115 | vector::push_back(&mut vec, 33); 116 | vector::push_back(&mut vec, 66); 117 | vector::push_back(&mut vec, 99); 118 | vector::push_back(&mut vec, 100); 119 | vector::push_back(&mut vec, 123); 120 | vector::push_back(&mut vec, 222); 121 | vector::push_back(&mut vec, 233); 122 | vector::push_back(&mut vec, 244); 123 | assert!(find_upper_bound(&vec, 223) == 6, 0); 124 | } 125 | 126 | #[test] 127 | fun test_lt() { 128 | assert!(lt(&x"19853428", &x"19853429"), 0); 129 | assert!(lt(&x"32432023", &x"32432323"), 1); 130 | assert!(!lt(&x"83975792", &x"83975492"), 2); 131 | assert!(!lt(&x"83975492", &x"83975492"), 3); 132 | } 133 | 134 | #[test] 135 | fun test_gt() { 136 | assert!(gt(&x"17432844", &x"17432843"), 0); 137 | assert!(gt(&x"79847429", &x"79847329"), 1); 138 | assert!(!gt(&x"19849334", &x"19849354"), 2); 139 | assert!(!gt(&x"19849354", &x"19849354"), 3); 140 | } 141 | 142 | #[test] 143 | fun test_not_gt() { 144 | assert!(lte(&x"23789179", &x"23789279"), 0); 145 | assert!(lte(&x"23789279", &x"23789279"), 1); 146 | assert!(!lte(&x"13258445", &x"13258444"), 2); 147 | assert!(!lte(&x"13258454", &x"13258444"), 3); 148 | } 149 | 150 | #[test] 151 | fun test_lte() { 152 | assert!(lte(&x"23789179", &x"23789279"), 0); 153 | assert!(lte(&x"23789279", &x"23789279"), 1); 154 | assert!(!lte(&x"13258445", &x"13258444"), 2); 155 | assert!(!lte(&x"13258454", &x"13258444"), 3); 156 | } 157 | 158 | #[test] 159 | fun test_gte() { 160 | assert!(gte(&x"14329932", &x"14329832"), 0); 161 | assert!(gte(&x"14329832", &x"14329832"), 1); 162 | assert!(!gte(&x"12654586", &x"12654587"), 2); 163 | assert!(!gte(&x"12654577", &x"12654587"), 3); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /sui/sources/virtual_block.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | /// @title VirtualBlock 4 | /// @dev This module allows the creation of virtual blocks with transactions sorted by fees, turning transaction latency auctions into fee auctions. 5 | /// Once you've created a new mempool (specifying a miner fee rate and a block time/delay), simply add entries to the block, 6 | /// mine the entries (for a miner fee), and repeat. Extract mempool fees as necessary. 7 | module movemate::virtual_block { 8 | use std::errors; 9 | use std::vector; 10 | 11 | use sui::coin::{Self, Coin}; 12 | use sui::tx_context::{Self, TxContext}; 13 | 14 | use movemate::crit_bit::{Self, CB}; 15 | 16 | /// @dev When trying to mine a block before the block time has passed. 17 | const EBLOCK_TIME_NOT_PASSED: u64 = 0; 18 | 19 | /// @notice Struct for a virtual block with entries sorted by bids. 20 | struct Mempool has store { 21 | blocks: vector>>, 22 | current_block_bids: Coin, 23 | last_block_timestamp: u64, 24 | mempool_fees: Coin, 25 | miner_fee_rate: u64, 26 | block_time: u64 27 | } 28 | 29 | /// @notice Creates a new mempool (specifying miner fee rate and block time/delay). 30 | public fun new_mempool(miner_fee_rate: u64, block_time: u64, ctx: &mut TxContext): Mempool { 31 | Mempool { 32 | blocks: vector::singleton>>(crit_bit::empty>()), 33 | current_block_bids: coin::zero(ctx), 34 | last_block_timestamp: tx_context::epoch(ctx), 35 | mempool_fees: coin::zero(ctx), 36 | miner_fee_rate, 37 | block_time 38 | } 39 | } 40 | 41 | /// @notice Extracts all fees accrued by a mempool. 42 | public fun extract_mempool_fees(mempool: &mut Mempool, ctx: &mut TxContext): Coin { 43 | let value = coin::value(&mempool.mempool_fees); 44 | coin::take(coin::balance_mut(&mut mempool.mempool_fees), value, ctx) 45 | } 46 | 47 | /// @notice Adds an entry to the latest virtual block. 48 | public fun add_entry(mempool: &mut Mempool, entry: EntryType, bid: Coin) { 49 | // Add bid to block 50 | let bid_value = (coin::value(&bid) as u128); 51 | coin::join(&mut mempool.current_block_bids, bid); 52 | 53 | // Add entry to tree 54 | let len = vector::length(&mempool.blocks); 55 | let block = vector::borrow_mut(&mut mempool.blocks, len - 1); 56 | if (crit_bit::has_key(block, bid_value)) vector::push_back(crit_bit::borrow_mut(block, bid_value), entry) 57 | else crit_bit::insert(block, bid_value, vector::singleton(entry)); 58 | } 59 | 60 | /// @notice Validates the block time and distributes fees. 61 | public fun mine_entries(mempool: &mut Mempool, miner: address, ctx: &mut TxContext): CB> { 62 | // Validate time now >= last block time + block delay 63 | let now = tx_context::epoch(ctx); 64 | assert!(now >= mempool.last_block_timestamp + mempool.block_time, errors::invalid_state(EBLOCK_TIME_NOT_PASSED)); 65 | 66 | // Withdraw miner_fee_rate / 2**16 to the miner 67 | let miner_fee = coin::value(&mempool.current_block_bids) * mempool.miner_fee_rate / (1 << 16); 68 | coin::split_and_transfer(&mut mempool.current_block_bids, miner_fee, miner, ctx); 69 | 70 | // Send the rest to the mempool admin 71 | let remaining = coin::value(&mempool.current_block_bids); 72 | coin::join(&mut mempool.mempool_fees, coin::take(coin::balance_mut(&mut mempool.current_block_bids), remaining, ctx)); 73 | 74 | // Get last block 75 | let last_block = vector::pop_back(&mut mempool.blocks); 76 | 77 | // Create next block 78 | vector::push_back(&mut mempool.blocks, crit_bit::empty>()); 79 | *&mut mempool.last_block_timestamp = now; 80 | 81 | // Return entries of last block 82 | last_block 83 | } 84 | 85 | #[test_only] 86 | use sui::test_scenario; 87 | 88 | #[test_only] 89 | struct FakeEntry has store, drop { 90 | stuff: u64 91 | } 92 | 93 | #[test_only] 94 | struct FakeMoney { } 95 | 96 | #[test_only] 97 | struct TempMempool has key { 98 | mempool: Mempool 99 | } 100 | 101 | #[test_only] 102 | const TEST_MINER_ADDR: address = @0xA11CE; 103 | 104 | #[test] 105 | public entry fun test_end_to_end() { 106 | // Test scenario 107 | let scenario = &mut test_scenario::begin(&TEST_MINER_ADDR); 108 | 109 | // Mint fake coin 110 | let coin_in_a = coin::mint_for_testing(1234000000, test_scenario::ctx(scenario)); 111 | let coin_in_b = coin::mint_for_testing(5678000000, test_scenario::ctx(scenario)); 112 | 113 | // create mempool 114 | let mempool = new_mempool(1 << 14, 5, test_scenario::ctx(scenario)); // 25% miner fee rate and 5 epoch block time 115 | 116 | // add entry 117 | add_entry(&mut mempool, FakeEntry { stuff: 1234 }, coin_in_a); 118 | 119 | // fast forward and add entry 120 | test_scenario::next_epoch(scenario); 121 | test_scenario::next_epoch(scenario); 122 | test_scenario::next_epoch(scenario); 123 | add_entry(&mut mempool, FakeEntry { stuff: 5678 }, coin_in_b); 124 | 125 | // fast forward and mine block 126 | test_scenario::next_epoch(scenario); 127 | test_scenario::next_epoch(scenario); 128 | test_scenario::next_epoch(scenario); 129 | let cb = mine_entries(&mut mempool, TEST_MINER_ADDR, test_scenario::ctx(scenario)); 130 | test_scenario::next_tx(scenario, &TEST_MINER_ADDR); 131 | let coin_miner = test_scenario::take_owned>(scenario); 132 | assert!(coin::value(&coin_miner) == (1234000000 + 5678000000) / 4, 0); 133 | test_scenario::return_owned(scenario, coin_miner); 134 | 135 | // Loop through highest to lowest bid 136 | let last_bid = 0xFFFFFFFFFFFFFFFF; 137 | 138 | while (!crit_bit::is_empty(&cb)) { 139 | let bid = crit_bit::max_key(&cb); 140 | assert!(bid < last_bid, 1); 141 | crit_bit::pop(&mut cb, bid); 142 | }; 143 | 144 | crit_bit::destroy_empty(cb); 145 | 146 | // extract mempool fees 147 | let mempool_fees = extract_mempool_fees(&mut mempool, test_scenario::ctx(scenario)); 148 | assert!(coin::value(&mempool_fees) == (1234000000 + 5678000000) - ((1234000000 + 5678000000) / 4), 2); 149 | 150 | // clean up: we can't drop coins so we burn them 151 | coin::destroy_for_testing(mempool_fees); 152 | 153 | // clean up: we can't drop mempool so we store it 154 | sui::transfer::transfer(TempMempool { 155 | mempool, 156 | }, TEST_MINER_ADDR); 157 | } 158 | 159 | #[test] 160 | #[expected_failure(abort_code = 0x001)] 161 | public entry fun test_mine_before_time() { 162 | // Test scenario 163 | let scenario = &mut test_scenario::begin(&TEST_MINER_ADDR); 164 | 165 | // Mint fake coin 166 | let coin_in = coin::mint_for_testing(1234000000, test_scenario::ctx(scenario)); 167 | 168 | // create mempool 169 | let mempool = new_mempool(1 << 14, 5, test_scenario::ctx(scenario)); // 25% miner fee rate and 5 epoch block time 170 | 171 | // add entry 172 | add_entry(&mut mempool, FakeEntry { stuff: 1234 }, coin_in); 173 | 174 | // fast forward and try to mine 175 | test_scenario::next_epoch(scenario); 176 | test_scenario::next_epoch(scenario); 177 | test_scenario::next_epoch(scenario); 178 | let cb = mine_entries(&mut mempool, TEST_MINER_ADDR, test_scenario::ctx(scenario)); 179 | 180 | // destroy cb tree 181 | crit_bit::pop(&mut cb, 1234); 182 | crit_bit::destroy_empty(cb); 183 | 184 | // clean up: we can't drop mempool so we store it 185 | sui::transfer::transfer(TempMempool { 186 | mempool, 187 | }, TEST_MINER_ADDR); 188 | } 189 | } 190 | --------------------------------------------------------------------------------