-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add DapperStorageRent to a few templates, update tests #98
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,247 @@ | ||
import FungibleToken from "./FungibleToken.cdc" | ||
import FlowToken from "./FlowToken.cdc" | ||
import PrivateReceiverForwarder from "./PrivateReceiverForwarder.cdc" | ||
|
||
/// DapperStorageRent | ||
/// Provide a means for accounts storage TopUps. To be used during transaction execution. | ||
pub contract DapperStorageRent { | ||
|
||
pub let DapperStorageRentAdminStoragePath: StoragePath | ||
|
||
/// Threshold of storage required to trigger a refill | ||
access(contract) var StorageRentRefillThreshold: UInt64 | ||
andrewdamelio marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// List of all refilledAccounts | ||
access(contract) var RefilledAccounts: [Address] | ||
/// Detailed account information of refilled accounts | ||
access(contract) var RefilledAccountInfos: {Address: RefilledAccountInfo} | ||
/// List of all blockedAccounts | ||
access(contract) var BlockedAccounts: [Address] | ||
/// Blocks required between refill attempts | ||
access(contract) var RefillRequiredBlocks: UInt64 | ||
|
||
/// Event emitted when an Admin blocks an address | ||
pub event BlockedAddress(_ address: [Address]) | ||
andrewdamelio marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// Event emitted when a Refill is successful | ||
pub event Refuelled(_ address: Address) | ||
/// Event emitted when a Refill is not successful | ||
pub event RefilledFailed(address: Address, reason: String) | ||
|
||
/// getStorageRentRefillThreshold | ||
/// Get the current StorageRentRefillThreshold | ||
/// | ||
/// @return UInt64 value of the current StorageRentRefillThreshold value | ||
pub fun getStorageRentRefillThreshold(): UInt64 { | ||
return self.StorageRentRefillThreshold | ||
} | ||
|
||
/// getRefilledAccounts | ||
/// Get the current StorageRentRefillThreshold | ||
/// | ||
/// @return List of refilled Accounts | ||
pub fun getRefilledAccounts(): [Address] { | ||
return self.RefilledAccounts | ||
} | ||
|
||
/// getBlockedAccounts | ||
/// Get the current StorageRentRefillThreshold | ||
/// | ||
/// @return List of blocked accounts | ||
pub fun getBlockedAccounts() : [Address] { | ||
return self.BlockedAccounts | ||
} | ||
|
||
/// getRefilledAccountInfos | ||
/// Get the current StorageRentRefillThreshold | ||
/// | ||
/// @return Address: RefilledAccountInfo mapping | ||
pub fun getRefilledAccountInfos(): {Address: RefilledAccountInfo} { | ||
return self.RefilledAccountInfos | ||
} | ||
|
||
/// getRefillRequiredBlocks | ||
/// Get the current StorageRentRefillThreshold | ||
/// | ||
/// @return UInt64 value of the current RefillRequiredBlocks value | ||
pub fun getRefillRequiredBlocks(): UInt64 { | ||
return self.RefillRequiredBlocks | ||
} | ||
|
||
/// tryRefill | ||
/// Attempt to refill an accounts storage capacity if it has dipped below threshold and passes other checks. | ||
/// | ||
/// @param address: Address to attempt a storage refill on | ||
pub fun tryRefill(_ address: Address) { | ||
let REFUEL_AMOUNT = 0.06; | ||
andrewdamelio marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
self.cleanExpiredRefilledAccounts(10) | ||
|
||
// Get the Flow Token reciever of the address | ||
let recipient = getAccount(address) | ||
let receiverRef = recipient.getCapability<&PrivateReceiverForwarder.Forwarder>(PrivateReceiverForwarder.PrivateReceiverPublicPath).borrow() | ||
|
||
// Sliently fail if the receiverRef is nill | ||
andrewdamelio marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if receiverRef == nil || receiverRef!.owner == nil { | ||
emit RefilledFailed(address: address, reason: "Couldn't borrow the Accounts flowTokenVault") | ||
return | ||
} | ||
|
||
// Sliently fail if the account has already be refueled within the block allowance | ||
andrewdamelio marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if self.RefilledAccountInfos[address] != nil && getCurrentBlock().height - self.RefilledAccountInfos[address]!.atBlock < self.RefillRequiredBlocks { | ||
emit RefilledFailed(address: address, reason: "RefillRequiredBlocks") | ||
return | ||
} | ||
|
||
// Get the users storage capacity and usage values | ||
var low: UInt64 = recipient.storageUsed | ||
var high: UInt64 = recipient.storageCapacity | ||
|
||
if high < low { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is it ever possible? that storageUsed > storageCapacity ? If it is, then it is big bug |
||
high <-> low | ||
} | ||
|
||
// Sliently fail if the account has been blocked from receiving refills | ||
andrewdamelio marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if DapperStorageRent.getBlockedAccounts().contains(address) { | ||
emit RefilledFailed(address: address, reason: "Address is Blocked") | ||
return | ||
} | ||
|
||
// If the user is below the threshold PrivateReceiverForwarder will send 0.06 Flow tokens for about 6MB of storage | ||
if high - low < self.StorageRentRefillThreshold { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remember that the value of |
||
let vaultRef = self.account.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) | ||
if vaultRef == nil { | ||
emit RefilledFailed(address: address, reason: "Couldn't borrow the Accounts FlowToken.Vault") | ||
return | ||
} | ||
|
||
let privateForwardingSenderRef = self.account.borrow<&PrivateReceiverForwarder.Sender>(from: PrivateReceiverForwarder.SenderStoragePath) | ||
if privateForwardingSenderRef == nil { | ||
emit RefilledFailed(address: address, reason: "Couldn't borrow the Accounts PrivateReceiverForwarder") | ||
return | ||
} | ||
|
||
// Check to make sure the payment vault has sufficient funds | ||
if let vaultBalanceRef = self.account.getCapability(/public/flowTokenBalance).borrow<&FlowToken.Vault{FungibleToken.Balance}>() { | ||
if vaultBalanceRef.balance <= REFUEL_AMOUNT { | ||
emit RefilledFailed(address: address, reason: "Insufficient balance to refuel") | ||
return | ||
} | ||
} else { | ||
emit RefilledFailed(address: address, reason: "Couldn't borrow flowToken balance") | ||
return | ||
} | ||
|
||
// 0.06 = 6MB of storage, or ~20k NBA TS moments | ||
privateForwardingSenderRef!.sendPrivateTokens(address,tokens:<-vaultRef!.withdraw(amount: REFUEL_AMOUNT)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't make sure that the user would receive the 0.06 FLOW in the given account as the user can have a different receiver capability of different accounts in the private forwarder resource. |
||
self.addRefilledAccount(address) | ||
emit Refuelled(address) | ||
} else { | ||
emit RefilledFailed(address: address, reason: "Address is not below StorageRentRefillThreshold") | ||
} | ||
} | ||
|
||
/// checkEligibility | ||
/// | ||
/// @param address: Address to check eligibility on | ||
/// @return Boolean valued based on if the provided address is below the storage threshold | ||
pub fun checkEligibility(_ address: Address): Bool { | ||
if self.RefilledAccountInfos[address] != nil && getCurrentBlock().height - self.RefilledAccountInfos[address]!.atBlock < self.RefillRequiredBlocks { | ||
return false | ||
} | ||
let acct = getAccount(address) | ||
var high: UInt64 = acct.storageCapacity | ||
var low: UInt64 = acct.storageUsed | ||
if high < low { | ||
high <-> low | ||
} | ||
|
||
if high - low >= self.StorageRentRefillThreshold { | ||
return false | ||
} | ||
|
||
return true | ||
} | ||
|
||
/// addRefilledAccount | ||
/// | ||
/// @param address: Address to add to RefilledAccounts/RefilledAccountInfos | ||
access(contract) fun addRefilledAccount(_ address: Address) { | ||
if self.RefilledAccountInfos[address] != nil { | ||
self.RefilledAccounts.remove(at: self.RefilledAccountInfos[address]!.index) | ||
} | ||
|
||
self.RefilledAccounts.append(address) | ||
self.RefilledAccountInfos[address] = RefilledAccountInfo(self.RefilledAccounts.length-1, getCurrentBlock().height) | ||
} | ||
|
||
/// cleanExpiredRefilledAccounts | ||
/// public method to clean up expired accounts based on current block height | ||
/// | ||
/// @param batchSize: Int to set the batch size of the cleanup | ||
pub fun cleanExpiredRefilledAccounts(_ batchSize: Int) { | ||
var index = 0 | ||
while index < batchSize && self.RefilledAccounts.length > index { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is incorrect as it would only allow running the loop half times than the given batchSize. If the given batchSize is 10, this loop will run 5 times because of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice catch!! working on a fix There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @satyamakgec updated method to the below, thoughts?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can avoid the below
|
||
if self.RefilledAccountInfos[self.RefilledAccounts[index]] != nil && | ||
getCurrentBlock().height - self.RefilledAccountInfos[self.RefilledAccounts[index]]!.atBlock < self.RefillRequiredBlocks { | ||
break | ||
} | ||
|
||
self.RefilledAccountInfos.remove(key: self.RefilledAccounts[index]) | ||
self.RefilledAccounts.remove(at: index) | ||
index = index + 1 | ||
} | ||
} | ||
|
||
/// RefilledAccountInfo struct | ||
/// Holds the block number it was refilled at | ||
pub struct RefilledAccountInfo { | ||
pub let atBlock: UInt64 | ||
pub let index: Int | ||
|
||
init(_ index: Int, _ atBlock: UInt64) { | ||
self.index = index | ||
self.atBlock = atBlock | ||
} | ||
} | ||
|
||
/// Admin resource | ||
/// Used to set different configuration levers such as StorageRentRefillThreshold, RefillRequiredBlocks, and BlockedAccounts | ||
pub resource Admin { | ||
pub fun setStorageRentRefillThreshold(_ threshold: UInt64) { | ||
DapperStorageRent.StorageRentRefillThreshold = threshold | ||
} | ||
|
||
pub fun setRefillRequiredBlocks(_ blocks: UInt64) { | ||
DapperStorageRent.RefillRequiredBlocks = blocks | ||
} | ||
|
||
pub fun blockAddress(_ address: Address) { | ||
if !DapperStorageRent.getBlockedAccounts().contains(address) { | ||
DapperStorageRent.BlockedAccounts.append(address) | ||
emit BlockedAddress(DapperStorageRent.getBlockedAccounts()) | ||
} | ||
} | ||
|
||
pub fun unblockAddress(_ address: Address) { | ||
if DapperStorageRent.getBlockedAccounts().contains(address) { | ||
let position = DapperStorageRent.BlockedAccounts.firstIndex(of: address) ?? panic("Trying to unblock an address that is not blocked.") | ||
if position != nil { | ||
DapperStorageRent.BlockedAccounts.remove(at: position) | ||
emit BlockedAddress(DapperStorageRent.getBlockedAccounts()) | ||
} | ||
} | ||
} | ||
} | ||
|
||
// DapperStorageRent init | ||
init() { | ||
self.DapperStorageRentAdminStoragePath = /storage/DapperStorageRentAdmin | ||
self.StorageRentRefillThreshold = 5000 | ||
self.RefilledAccounts = [] | ||
self.RefilledAccountInfos = {} | ||
self.RefillRequiredBlocks = 86400 | ||
self.BlockedAccounts = [] | ||
|
||
let admin <- create Admin() | ||
self.account.save(<-admin, to: self.DapperStorageRentAdminStoragePath) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import FungibleToken from "./FungibleToken.cdc" | ||
|
||
pub contract PrivateReceiverForwarder { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand why we need this contract? can't we directly transfer the funds to the address capability? because it also holds the capability for the same. Maybe I am missing something here ? |
||
|
||
// Event that is emitted when tokens are deposited to the target receiver | ||
pub event PrivateDeposit(amount: UFix64, to: Address?) | ||
|
||
pub let SenderStoragePath: StoragePath | ||
|
||
pub let PrivateReceiverStoragePath: StoragePath | ||
pub let PrivateReceiverPublicPath: PublicPath | ||
|
||
pub resource Forwarder { | ||
|
||
// This is where the deposited tokens will be sent. | ||
// The type indicates that it is a reference to a receiver | ||
// | ||
access(self) var recipient: Capability<&{FungibleToken.Receiver}> | ||
|
||
// deposit | ||
// | ||
// Function that takes a Vault object as an argument and forwards | ||
// it to the recipient's Vault using the stored reference | ||
// | ||
access(contract) fun deposit(from: @FungibleToken.Vault) { | ||
let receiverRef = self.recipient.borrow()! | ||
|
||
let balance = from.balance | ||
|
||
receiverRef.deposit(from: <-from) | ||
|
||
emit PrivateDeposit(amount: balance, to: self.owner?.address) | ||
} | ||
|
||
init(recipient: Capability<&{FungibleToken.Receiver}>) { | ||
pre { | ||
recipient.borrow() != nil: "Could not borrow Receiver reference from the Capability" | ||
} | ||
self.recipient = recipient | ||
} | ||
} | ||
|
||
// createNewForwarder creates a new Forwarder reference with the provided recipient | ||
// | ||
pub fun createNewForwarder(recipient: Capability<&{FungibleToken.Receiver}>): @Forwarder { | ||
return <-create Forwarder(recipient: recipient) | ||
} | ||
|
||
|
||
pub resource Sender { | ||
pub fun sendPrivateTokens(_ address: Address, tokens: @FungibleToken.Vault) { | ||
|
||
let account = getAccount(address) | ||
|
||
let privateReceiver = account.getCapability<&PrivateReceiverForwarder.Forwarder>(PrivateReceiverForwarder.PrivateReceiverPublicPath) | ||
.borrow() ?? panic("Could not borrow reference to private forwarder") | ||
|
||
privateReceiver.deposit(from: <-tokens) | ||
|
||
} | ||
|
||
pub fun replicate(): @Sender { | ||
return <-create Sender() | ||
} | ||
} | ||
|
||
init() { | ||
|
||
self.SenderStoragePath = /storage/PrivateSender | ||
|
||
self.PrivateReceiverStoragePath = /storage/PrivateReceiver | ||
self.PrivateReceiverPublicPath = /public/PrivateReceiver | ||
|
||
self.account.save(<-create Sender(), to: self.SenderStoragePath) | ||
|
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it essential to have the prefix of the name
Dapper
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, not essential, but was thinking it gave some good context to its relationship to BloctoStorageRent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm.. I think it is better to remove and not use the company name as the prefix. Instead, we can go with a generic contract name to avoid some "XYZ" reasons. We can rename it to
Refueler
or something like that.