-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ECO-1988] Add lottery wrapper implementation (#168)
- Loading branch information
Showing
4 changed files
with
196 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,6 +51,7 @@ nazar | |
nextjs | ||
nohup | ||
octa | ||
octas | ||
oden | ||
parallelizable | ||
permissionless | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
[addresses] | ||
integrator = "_" | ||
rewards = "_" | ||
|
||
[dependencies.EmojicoinDotFun] | ||
local = "../emojicoin_dot_fun" | ||
|
||
[dev-addresses] | ||
coin_factory = "0xaaaa" | ||
emojicoin_dot_fun = "0xc0de" | ||
integrator = "0xccc" | ||
rewards = "0xddd" | ||
|
||
[package] | ||
authors = ["Econia Labs ([email protected])"] | ||
name = "EmojicoinDotFunRewards" | ||
upgrade_policy = "compatible" | ||
version = "1.0.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
<!--- | ||
cspell:word undergassing | ||
cspell:word permissionlessly | ||
--> | ||
|
||
# `EmojicoinDotFunRewards` | ||
|
||
This package provides an overloaded swap function that randomly rewards a fixed | ||
percentage of swaps that exceed an arbitrary amount of volume. Funds are | ||
autonomously disbursed from a resource account with an APT vault that can be | ||
topped off permissionlessly. | ||
|
||
Per the [undergassing docs], one strategy to mitigate undergassing attacks is to | ||
make the most preferred path cost the most gas. Hence, here it is ensured that | ||
the winning path requires the most gas per the final logical branch: | ||
|
||
```move | ||
let result = randomness::u64_range(0, WIN_PERCENTAGE_DENOMINATOR); | ||
if (result < WIN_PERCENTAGE_NUMERATOR) { | ||
// If user wins ... | ||
``` | ||
|
||
To view the bytecode result: | ||
|
||
```sh | ||
aptos move compile --dev | ||
aptos move disassemble --bytecode-path \ | ||
build/EmojicoinDotFunRewards/bytecode_modules/emojicoin_dot_fun_rewards.mv | ||
``` | ||
|
||
Then inside `build/EmojicoinDotFunRewards/bytecode_modules`, open | ||
`emojicoin_dot_fun_rewards.asm` and note: | ||
|
||
```asm | ||
B4: | ||
56: LdU64(0) | ||
57: LdConst[6](U64: [16, 39, 0, 0, 0, 0, 0, 0]) | ||
58: Call randomness::u64_range(u64, u64): u64 | ||
59: LdConst[7](U64: [231, 3, 0, 0, 0, 0, 0, 0]) | ||
60: Lt | ||
61: BrFalse(85) | ||
``` | ||
|
||
Here, the `false` (losing) branch is essentially an instant return, while the | ||
`true` (winning) branch requires additional logic. | ||
|
||
Note that even if the `BrFalse` instruction were to be exceptionally expensive | ||
for the `false` branch, it would still not be able to make the losing path more | ||
expensive in the general case, because the winning path *also* has a `BrFalse` | ||
statement where the preferred case is the `false` outcome, specifically: | ||
|
||
```move | ||
let reward_amount = if (vault_balance < REWARD_AMOUNT_IN_OCTAS) vault_balance | ||
else REWARD_AMOUNT_IN_OCTAS; | ||
``` | ||
|
||
Hence the winning path is necessarily more expensive than the losing path | ||
because it contains all of the same instructions as the losing path, and more, | ||
except in the extreme hypothetical edge case where: | ||
|
||
1. `BrFalse` for the `false` branch is set absurdly high in a gas schedule | ||
update, such that its execution cost vastly exceeds all other instructions in | ||
the winning path combined. | ||
1. The user is the last lottery winner, sweeping a vault that has less than the | ||
single rewards amount inside. | ||
|
||
Yet even this can issue can be mitigated, by simply filling up the vault with | ||
an integer number of standard reward amounts, because then the lottery will | ||
never evaluate due to this statement: | ||
|
||
```move | ||
if (vault_balance == 0) return; | ||
``` | ||
|
||
[undergassing docs]: https://aptos.dev/en/build/smart-contracts/randomness#undergasing-attacks-and-how-to-prevent |
102 changes: 102 additions & 0 deletions
102
src/move/rewards/sources/emojicoin_dot_fun_rewards.move
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
module rewards::emojicoin_dot_fun_rewards { | ||
|
||
use std::signer; | ||
use aptos_framework::account::{Self, SignerCapability}; | ||
use aptos_framework::aptos_account; | ||
use aptos_framework::aptos_coin::AptosCoin; | ||
use aptos_framework::coin; | ||
use aptos_framework::event; | ||
use aptos_framework::randomness; | ||
use emojicoin_dot_fun::emojicoin_dot_fun::{Self, Swap}; | ||
|
||
/// Resource account address seed for the vault. | ||
const VAULT: vector<u8> = b"Vault"; | ||
|
||
/// Flat integrator fee. | ||
const INTEGRATOR_FEE_RATE_BPS: u8 = 100; | ||
|
||
// APT amounts. | ||
const REWARD_AMOUNT_IN_OCTAS: u64 = 100_000_000; | ||
const VOLUME_THRESHOLD_IN_OCTAS: u64 = 990_000_000; | ||
|
||
// Randomness amounts. | ||
const WIN_PERCENTAGE_NUMERATOR: u64 = 980; | ||
const WIN_PERCENTAGE_DENOMINATOR: u64 = 10_000; | ||
|
||
struct RewardsVaultSignerCapability has key { | ||
signer_capability: SignerCapability, | ||
} | ||
|
||
#[event] | ||
struct EmojicoinDotFunRewardsLotteryWinner has copy, drop, store { | ||
swap: Swap, | ||
reward_amount: u64, | ||
} | ||
|
||
#[randomness] | ||
entry fun swap_with_rewards<Emojicoin, EmojicoinLP>( | ||
swapper: &signer, | ||
market_address: address, | ||
input_amount: u64, | ||
is_sell: bool, | ||
) acquires RewardsVaultSignerCapability { | ||
|
||
// Simulate swap to get quote volume, then execute swap. | ||
let swapper_address = signer::address_of(swapper); | ||
let swap = emojicoin_dot_fun::simulate_swap( | ||
swapper_address, | ||
market_address, | ||
input_amount, | ||
is_sell, | ||
@integrator, | ||
INTEGRATOR_FEE_RATE_BPS, | ||
); | ||
let ( _, _, _, _, _, _, _, _, _, _, quote_volume, _, _, _, _, _) = | ||
emojicoin_dot_fun::unpack_swap(swap); | ||
emojicoin_dot_fun::swap<Emojicoin, EmojicoinLP>( | ||
swapper, | ||
market_address, | ||
input_amount, | ||
is_sell, | ||
@integrator, | ||
INTEGRATOR_FEE_RATE_BPS, | ||
); | ||
|
||
// Return if quote volume is below threshold, otherwise proceed to lottery. | ||
if (quote_volume < VOLUME_THRESHOLD_IN_OCTAS) return; | ||
|
||
// Get vault balance, returning without lottery if vault is empty. | ||
let vault_signer_cap_ref = | ||
&borrow_global<RewardsVaultSignerCapability>(@rewards).signer_capability; | ||
let vault_address = account::get_signer_capability_address(vault_signer_cap_ref); | ||
let vault_balance = coin::balance<AptosCoin>(vault_address); | ||
if (vault_balance == 0) return; | ||
|
||
// Evaluate lottery. | ||
let result = randomness::u64_range(0, WIN_PERCENTAGE_DENOMINATOR); | ||
if (result < WIN_PERCENTAGE_NUMERATOR) { | ||
let reward_amount = if (vault_balance < REWARD_AMOUNT_IN_OCTAS) vault_balance | ||
else REWARD_AMOUNT_IN_OCTAS; | ||
let vault_signer = account::create_signer_with_capability(vault_signer_cap_ref); | ||
aptos_account::transfer(&vault_signer, swapper_address, reward_amount); | ||
event::emit(EmojicoinDotFunRewardsLotteryWinner{ swap, reward_amount }); | ||
}; | ||
|
||
} | ||
|
||
fun init_module(rewards: &signer) { | ||
let (vault_signer, signer_capability) = account::create_resource_account(rewards, VAULT); | ||
move_to(rewards, RewardsVaultSignerCapability{ signer_capability }); | ||
coin::register<AptosCoin>(&vault_signer); | ||
} | ||
|
||
#[test] fun expected_value() { | ||
let bps_per_unit = (emojicoin_dot_fun::get_BASIS_POINTS_PER_UNIT() as u64); | ||
assert!( | ||
WIN_PERCENTAGE_NUMERATOR * REWARD_AMOUNT_IN_OCTAS / WIN_PERCENTAGE_DENOMINATOR < | ||
VOLUME_THRESHOLD_IN_OCTAS * (INTEGRATOR_FEE_RATE_BPS as u64) / bps_per_unit, | ||
0 | ||
); | ||
} | ||
|
||
} |