-
Notifications
You must be signed in to change notification settings - Fork 594
/
Copy pathcode.vy
117 lines (103 loc) · 3.8 KB
/
code.vy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#pragma version >0.3.10
# Basic implementation of a sublinear staking contract. Stake coins, and you get
# return proportional to coins staked ** 0.75. Returns last as long as coins last
from ethereum.ercs import IERC20 as ERC20
# Define the interface for the ERC-1155 contract
interface ERC1155:
def balanceOf(_owner: address, _id: uint256) -> uint256: view
stakedAmount: public(HashMap[address, uint256])
stakeLastUpdated: public(HashMap[address, uint256])
STAKED_TOKEN_ADDRESS: immutable(ERC20)
UNIQUEID_TOKEN_ADDRESS: immutable(ERC1155)
UNIQUEID_TOKEN_COLLECTION: immutable(uint256)
REWARD_DENOMINATOR: immutable(uint256)
totalPayoutPerSlot: uint256
liabilities: uint256
liabilitiesLastUpdated: uint256
# If you stake x coins, this is the return you get per slot
@view
def getReturnPerSlot(x: uint256) -> uint256:
return isqrt(x * isqrt(x)) // REWARD_DENOMINATOR
@view
def isEligible(user: address) -> bool:
# Get the balance of the user for the specified token ID
balance: uint256 = staticcall UNIQUEID_TOKEN_ADDRESS.balanceOf(
user,
UNIQUEID_TOKEN_COLLECTION
)
# Return True if balance is greater than zero, else False
return balance > 0
# Setup global variables
@deploy
def __init__(stakedTokenAddress: address,
uniqueidTokenAddress: address,
uniqueidTokenCollection: uint256,
rewardDenominator: uint256):
assert stakedTokenAddress.is_contract
assert uniqueidTokenAddress.is_contract
STAKED_TOKEN_ADDRESS = ERC20(stakedTokenAddress)
UNIQUEID_TOKEN_ADDRESS = ERC1155(uniqueidTokenAddress)
UNIQUEID_TOKEN_COLLECTION = uniqueidTokenCollection
REWARD_DENOMINATOR = rewardDenominator
# Stake the specified number of tokens
@external
def stake(amount: uint256):
assert self.isEligible(msg.sender)
assert self.stakedAmount[msg.sender] == 0
assert amount > 0
returnPerSlot: uint256 = self.getReturnPerSlot(amount)
correctedNow: uint256 = min(block.timestamp, self._fundedUntil())
self.stakedAmount[msg.sender] = amount
self.stakeLastUpdated[msg.sender] = correctedNow
# The contract tracks liabilities and totalPayoutPerSlot, so that it knows
# how long it can keep paying rewards
self.liabilities += (
(correctedNow - self.liabilitiesLastUpdated)
* self.totalPayoutPerSlot
)
self.liabilities += amount
self.liabilitiesLastUpdated = correctedNow
self.totalPayoutPerSlot += returnPerSlot
success: bool = extcall STAKED_TOKEN_ADDRESS.transferFrom(
msg.sender,
self,
amount,
default_return_value=True
)
assert success
# Remove your stake, plus any returns
def _unstake() -> uint256:
returnPerSlot: uint256 = self.getReturnPerSlot(self.stakedAmount[msg.sender])
correctedNow: uint256 = min(block.timestamp, self._fundedUntil())
timeElapsed: uint256 = correctedNow - self.stakeLastUpdated[msg.sender]
totalOut: uint256 = self.stakedAmount[msg.sender] + timeElapsed * returnPerSlot
self.stakedAmount[msg.sender] = 0
self.liabilities += (
(correctedNow - self.liabilitiesLastUpdated)
* self.totalPayoutPerSlot
)
self.liabilitiesLastUpdated = correctedNow
self.totalPayoutPerSlot -= returnPerSlot
self.liabilities -= totalOut
success: bool = extcall STAKED_TOKEN_ADDRESS.transfer(
msg.sender,
totalOut,
default_return_value=True
)
assert success
return totalOut
@external
def unstake() -> uint256:
return self._unstake()
# How long the contract can keep paying returns
@view
def _fundedUntil() -> uint256:
return (
self.liabilitiesLastUpdated
+ (staticcall STAKED_TOKEN_ADDRESS.balanceOf(self) - self.liabilities)
// max(self.totalPayoutPerSlot, 1)
)
@external
@view
def fundedUntil() -> uint256:
return self._fundedUntil()