Skip to content

Commit

Permalink
add test to validate access to child account tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
austinkline committed Sep 24, 2024
1 parent 9d8a2d2 commit d5e6fe5
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 2 deletions.
3 changes: 3 additions & 0 deletions contracts/ContractManager.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ access(all) contract ContractManager {

acct.storage.borrow<&{FungibleToken.Receiver}>(from: /storage/flowTokenVault)!.deposit(from: <-tokens)

// setup a provider capability so that tokens are accessible via hybrid custody
acct.capabilities.storage.issue<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>(/storage/flowTokenVault)

let router <- FungibleTokenRouter.createRouter(defaultAddress: defaultRouterAddress)
acct.storage.save(<-router, to: FungibleTokenRouter.StoragePath)

Expand Down
14 changes: 14 additions & 0 deletions scripts/util/get_withdraw_controller_id.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import "FungibleToken"

access(all) fun main(addr: Address, path: StoragePath): UInt64 {
let acct = getAuthAccount<auth(Capabilities) &Account>(addr)

let type = Type<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>()
for controller in acct.capabilities.storage.getControllers(forPath: path) {
if controller.borrowType.isSubtype(of: type) {
return controller.capabilityID
}
}

panic("no withdraw capability ID found")
}
30 changes: 30 additions & 0 deletions tests/ContractManager_tests.cdc
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Test
import "./test_helpers.cdc"
import "ContractManager"
import "HybridCustody"
import "FungibleToken"

access(all) fun setup() {
deployAll()
Expand All @@ -14,4 +16,32 @@ access(all) fun test_SetupContractManager() {

let savedEvent = Test.eventsOfType(Type<ContractManager.ManagerSaved>()).removeLast() as! ContractManager.ManagerSaved
Test.assertEqual(acct.address, savedEvent.ownerAddress)
}

access(all) fun test_SetupContractManager_CanWithdrawTokens() {
let acct = Test.createAccount()
mintFlowTokens(acct, 10.0)

let amount = 5.0
txExecutor("contract-manager/setup.cdc", [acct], [amount])
let savedEvent = Test.eventsOfType(Type<ContractManager.ManagerSaved>()).removeLast() as! ContractManager.ManagerSaved
let contractAddress = savedEvent.contractAddress

// make sure there is a HybridCustody.AccountUpdated event
let updatedEvent = Test.eventsOfType(Type<HybridCustody.AccountUpdated>()).removeLast() as! HybridCustody.AccountUpdated
Test.assertEqual(acct.address, updatedEvent.parent!)
Test.assertEqual(contractAddress, updatedEvent.child)
Test.assertEqual(true, updatedEvent.active)

// withdraw and destroy 1 token to prove we are able to access an account's tokens
let controllerId = scriptExecutor("util/get_withdraw_controller_id.cdc", [contractAddress, /storage/flowTokenVault])! as! UInt64
txExecutor("flow-token/withdraw_tokens.cdc", [acct], [amount, controllerId])

let withdrawEvent = Test.eventsOfType(Type<FungibleToken.Withdrawn>()).removeLast() as! FungibleToken.Withdrawn
Test.assertEqual(amount, withdrawEvent.amount)
Test.assertEqual(contractAddress, withdrawEvent.from!)

let depositEvent = Test.eventsOfType(Type<FungibleToken.Deposited>()).removeLast() as! FungibleToken.Deposited
Test.assertEqual(amount, depositEvent.amount)
Test.assertEqual(acct.address, depositEvent.to!)
}
34 changes: 32 additions & 2 deletions transactions/contract-manager/setup.cdc
Original file line number Diff line number Diff line change
@@ -1,13 +1,43 @@
import "ContractManager"
import "FlowToken"
import "FungibleToken"
import "HybridCustody"
import "ViewResolver"

transaction(flowTokenAmount: UFix64) {
prepare(acct: auth(Storage, Capabilities) &Account) {
prepare(acct: auth(Storage, Capabilities, Inbox) &Account) {
let v = acct.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)!
let tokens <- v.withdraw(amount: flowTokenAmount) as! @FlowToken.Vault

acct.storage.save(<- ContractManager.createManager(tokens: <-tokens, defaultRouterAddress: acct.address), to: ContractManager.StoragePath)
acct.storage.borrow<auth(ContractManager.Manage) &ContractManager.Manager>(from: ContractManager.StoragePath)!.onSave()
let contractManager = acct.storage.borrow<auth(ContractManager.Manage) &ContractManager.Manager>(from: ContractManager.StoragePath)!
contractManager.onSave()

// there is a published hybrid custody capability to redeem
if acct.storage.borrow<&HybridCustody.Manager>(from: HybridCustody.ManagerStoragePath) == nil {
let m <- HybridCustody.createManager(filter: nil)
acct.storage.save(<- m, to: HybridCustody.ManagerStoragePath)

for c in acct.capabilities.storage.getControllers(forPath: HybridCustody.ManagerStoragePath) {
c.delete()
}

acct.capabilities.unpublish(HybridCustody.ManagerPublicPath)

acct.capabilities.publish(
acct.capabilities.storage.issue<&{HybridCustody.ManagerPublic}>(HybridCustody.ManagerStoragePath),
at: HybridCustody.ManagerPublicPath
)

acct.capabilities.storage.issue<auth(HybridCustody.Manage) &{HybridCustody.ManagerPrivate, HybridCustody.ManagerPublic}>(HybridCustody.ManagerStoragePath)
}

let inboxName = HybridCustody.getChildAccountIdentifier(acct.address)
let cap = acct.inbox.claim<auth(HybridCustody.Child) &{HybridCustody.AccountPrivate, HybridCustody.AccountPublic, ViewResolver.Resolver}>(inboxName, provider: contractManager.getAccount().address)
?? panic("child account cap not found")

let manager = acct.storage.borrow<auth(HybridCustody.Manage) &HybridCustody.Manager>(from: HybridCustody.ManagerStoragePath)
?? panic("manager no found")
manager.addAccount(cap: cap)
}
}
31 changes: 31 additions & 0 deletions transactions/flow-token/withdraw_tokens.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import "FlowToken"
import "FungibleToken"
import "HybridCustody"
import "ContractManager"
import "FungibleTokenMetadataViews"

transaction(amount: UFix64, controllerID: UInt64) {
prepare(acct: auth(Storage, Capabilities) &Account) {
let contractManager = acct.storage.borrow<auth(ContractManager.Manage) &ContractManager.Manager>(from: ContractManager.StoragePath)
?? panic("contract manager not found")

let manager = acct.storage.borrow<auth(HybridCustody.Manage) &HybridCustody.Manager>(from: HybridCustody.ManagerStoragePath)
?? panic("hybrid custody manager not found")

let child = manager.borrowAccount(addr: contractManager.getAccount().address)
?? panic("child account not found")

let cap = child.getCapability(controllerID: controllerID, type: Type<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>()) ?? panic("capability count not be borrowed")
let providerCap = cap as! Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>
let vault = providerCap.borrow() ?? panic("vault count not be borrowed")
let tokens <- vault.withdraw(amount: amount)

let ftVaultData = tokens.resolveView(Type<FungibleTokenMetadataViews.FTVaultData>())! as! FungibleTokenMetadataViews.FTVaultData

if acct.storage.type(at: ftVaultData.storagePath) == nil {
acct.storage.save(<- ftVaultData.createEmptyVault(), to: ftVaultData.storagePath)
}

acct.storage.borrow<&{FungibleToken.Vault}>(from: ftVaultData.storagePath)!.deposit(from: <-tokens)
}
}
Empty file.

0 comments on commit d5e6fe5

Please sign in to comment.