Skip to content

Commit

Permalink
create new branch as #125 get corrupted
Browse files Browse the repository at this point in the history
  • Loading branch information
satyamakgec authored and joshuahannan committed Mar 22, 2023
1 parent fae3075 commit ed4f379
Show file tree
Hide file tree
Showing 11 changed files with 254 additions and 4 deletions.
31 changes: 31 additions & 0 deletions contracts/FungibleTokenSwitchboard.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ pub contract FungibleTokenSwitchboard {
pub fun getSupportedVaultTypes(): {Type: Bool}
pub fun deposit(from: @FungibleToken.Vault)
pub fun safeDeposit(from: @FungibleToken.Vault): @FungibleToken.Vault?
pub fun checkReceiverByType(type: Type): Bool
pub fun safeBorrowByType(type: Type): &{FungibleToken.Receiver}?
}

/// The resource that stores the multiple fungible token receiver
Expand Down Expand Up @@ -251,6 +253,35 @@ pub contract FungibleTokenSwitchboard {
return nil
}

/// Checks that the capability tied to a type is valid
///
/// @param vaultType: The type of the ft vault whose capability needs to be checked
///
/// @return a boolean marking the capability for a type as valid or not
pub fun checkReceiverByType(type: Type): Bool {
if self.receiverCapabilities[type] == nil {
return false
}

return self.receiverCapabilities[type]!.check()
}

/// Gets the receiver assigned to a provided vault type.
/// This is necessary because without it, it is not possible to look under the hood and see if a capability
/// is of an expected type or not. This helps guard against infinitely chained TokenForwarding or other invalid
/// malicious kinds of updates that could prevent listings from being made that are valid on storefronts.
///
/// @param vaultType: The type of the ft vault whose capability needs to be checked
///
/// @return an optional receiver capability for consumers of the switchboard to check/validate on their own
pub fun safeBorrowByType(type: Type): &{FungibleToken.Receiver}? {
if !self.checkReceiverByType(type: type) {
return nil
}

return self.receiverCapabilities[type]!.borrow()
}

/// A getter function to know which tokens a certain switchboard
/// resource is prepared to receive.
///
Expand Down
33 changes: 32 additions & 1 deletion contracts/utility/TokenForwarding.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,22 @@ pub contract TokenForwarding {
// Event that is emitted when tokens are deposited to the target receiver
pub event ForwardedDeposit(amount: UFix64, from: Address?)

pub resource Forwarder: FungibleToken.Receiver {
pub resource interface ForwarderPublic {

/// Helper function to check whether set `recipient` capability
/// is not latent or the capability tied to a type is valid.
pub fun check(): Bool

/// Gets the receiver assigned to a recipient capability.
/// This is necessary because without it, it is not possible to look under the hood and see if a capability
/// is of an expected type or not. This helps guard against infinitely chained TokenForwarding or other invalid
/// malicious kinds of updates that could prevent listings from being made that are valid on storefronts.
///
/// @return an optional receiver capability for consumers of the TokenForwarding to check/validate on their own
pub fun safeBorrow(): &{FungibleToken.Receiver}?
}

pub resource Forwarder: FungibleToken.Receiver, ForwarderPublic {

// This is where the deposited tokens will be sent.
// The type indicates that it is a reference to a receiver
Expand All @@ -43,6 +58,22 @@ pub contract TokenForwarding {
emit ForwardedDeposit(amount: balance, from: self.owner?.address)
}

/// Helper function to check whether set `recipient` capability
/// is not latent or the capability tied to a type is valid.
pub fun check(): Bool {
return self.recipient.check<&{FungibleToken.Receiver}>()
}

/// Gets the receiver assigned to a recipient capability.
/// This is necessary because without it, it is not possible to look under the hood and see if a capability
/// is of an expected type or not. This helps guard against infinitely chained TokenForwarding or other invalid
/// malicious kinds of updates that could prevent listings from being made that are valid on storefronts.
///
/// @return an optional receiver capability for consumers of the TokenForwarding to check/validate on their own
pub fun safeBorrow(): &{FungibleToken.Receiver}? {
return self.recipient.borrow<&{FungibleToken.Receiver}>()
}

// changeRecipient changes the recipient of the forwarder to the provided recipient
//
pub fun changeRecipient(_ newRecipient: Capability) {
Expand Down
3 changes: 2 additions & 1 deletion flow.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@
"NonFungibleToken",
"MetadataViews",
"FungibleTokenMetadataViews",
"FungibleTokenSwitchboard"
"FungibleTokenSwitchboard",
"TokenForwarding"
]
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/js/test/core_features.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const setup_token_forwarding_tx = fs.readFileSync(path.resolve(__dirname, "./moc
const setup_demo_token_tx = fs.readFileSync(path.resolve(__dirname, "./mocks/transactions/setup_account_demo.cdc"), {encoding:'utf8', flag:'r'});
const safe_generic_transfer_tx = fs.readFileSync(path.resolve(__dirname, "./mocks/transactions/safe_generic_transfer.cdc"), {encoding:'utf8', flag:'r'});
const get_balance_read = fs.readFileSync(path.resolve(__dirname, "./mocks/transactions/scripts/get_balance.cdc"), {encoding:'utf8', flag:'r'});

const is_recipient_valid = fs.readFileSync(path.resolve(__dirname, "./../../../transactions/scripts/tokenForwarder/is_recipient_valid.cdc"), {encoding:'utf8', flag:'r'});


const token_contract_code = fs.readFileSync(path.resolve(__dirname, "./mocks/contracts/Token.cdc"), {encoding:'utf8', flag:'r'});
Expand Down
18 changes: 18 additions & 0 deletions lib/js/test/mocks/transactions/fix_receiver_linking.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import FungibleToken from "../../../../../contracts/FungibleToken.cdc"
import ExampleToken from "../../../../../contracts/ExampleToken.cdc"
import FungibleTokenSwitchboard from "../../../../../contracts/FungibleTokenSwitchboard.cdc"

transaction() {

prepare(signer: AuthAccount) {

signer.unlink(FungibleTokenSwitchboard.ReceiverPublicPath)

// Create a public capability to the Vault that only exposes
// the deposit function through the Receiver interface
signer.link<&ExampleToken.Vault{FungibleToken.Receiver}>(
ExampleToken.ReceiverPublicPath,
target: ExampleToken.VaultStoragePath
)
}
}
18 changes: 18 additions & 0 deletions lib/js/test/mocks/transactions/setup_infinite_loop_capability.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import FungibleToken from "../../../../../contracts/FungibleToken.cdc"
import ExampleToken from "../../../../../contracts/ExampleToken.cdc"
import FungibleTokenSwitchboard from "../../../../../contracts/FungibleTokenSwitchboard.cdc"

transaction() {

prepare(signer: AuthAccount) {

signer.unlink(ExampleToken.ReceiverPublicPath)

// Create a public capability to the Vault that only exposes
// the deposit function through the Receiver interface
signer.link<&ExampleToken.Vault{FungibleToken.Receiver}>(
FungibleTokenSwitchboard.ReceiverPublicPath,
target: ExampleToken.VaultStoragePath
)
}
}
82 changes: 81 additions & 1 deletion lib/js/test/switchboard.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ async function deployContract(param) {

const get_supported_vault_types = fs.readFileSync(path.resolve(__dirname, "./../../../transactions/scripts/get_supported_vault_types.cdc"), {encoding:'utf8', flag:'r'});
const get_vault_types = fs.readFileSync(path.resolve(__dirname, "./../../../transactions/scripts/switchboard/get_vault_types.cdc"), {encoding:'utf8', flag:'r'});
const check_receiver_by_type = fs.readFileSync(path.resolve(__dirname, "./../../../transactions/scripts/switchboard/check_receiver_by_type.cdc"), {encoding:'utf8', flag:'r'});
const get_balance = fs.readFileSync(path.resolve(__dirname, "./../../../transactions/scripts/get_balance.cdc"), {encoding:'utf8', flag:'r'});
const setup_infinite_loop_capability = fs.readFileSync(path.resolve(__dirname, "./mocks/transactions/setup_infinite_loop_capability.cdc"), {encoding:'utf8', flag:'r'});
const fix_receiver_linking = fs.readFileSync(path.resolve(__dirname, "./mocks/transactions/fix_receiver_linking.cdc"), {encoding:'utf8', flag:'r'});

// Defining the test suite for the fungible token switchboard
describe("fungibletokenswitchboard", ()=>{
Expand Down Expand Up @@ -114,7 +117,84 @@ describe("fungibletokenswitchboard", ()=>{
);
});

// Third test checks if switchboard user is able to remove ft token vault capabilities
// Third test to check whether added capabilities has infinite forwarding loop or not.
test("should be able validate the added capability", async () => {
// First step: setup switchboard
await shallPass(
sendTransaction({
name: "switchboard/setup_account",
args: [],
signers: [fungibleTokenSwitchboardUser]
})
);
//Second step: setup example token vault
await shallPass(
sendTransaction({
name: "setup_account",
args: [],
signers: [fungibleTokenSwitchboardUser]
})
);

//Third step: add example token vault capability
await shallPass(
sendTransaction({
name: "switchboard/add_vault_capability",
args: [],
signers: [fungibleTokenSwitchboardUser]
})
);

// Fourth step: setup infinite loop capability.
await shallPass(
sendTransaction({
code: setup_infinite_loop_capability,
args: [],
signers: [fungibleTokenSwitchboardUser]
})
);

// Execute script to validate that added capability is not valid anymore
const [result, e] = await executeScript({
code: check_receiver_by_type,
args: [fungibleTokenSwitchboardUser]
});
expect(result).toStrictEqual(false);

// Try to fund the capability with funds
await shallPass(
sendTransaction({
name: "switchboard/safe_transfer_tokens_v2",
args: [fungibleTokenSwitchboardUser, 10.0],
signers: [serviceAccount]
})
)

// Check balance and it should not increase as the transfer would not happen
const [balance, error] = await executeScript({
code: get_balance,
args: [fungibleTokenSwitchboardUser]
});
expect(parseFloat(balance)).toBeCloseTo(0);

// Let's fix the linking and transfer the funds
await shallPass(
sendTransaction({
code: fix_receiver_linking,
args: [],
signers: [fungibleTokenSwitchboardUser]
})
);

// Execute script to validate that added capability is valid
const [isValid, err] = await executeScript({
code: check_receiver_by_type,
args: [fungibleTokenSwitchboardUser]
});
expect(isValid).toStrictEqual(true);
});

// Fourth test checks if switchboard user is able to remove ft token vault capabilities
test("should be able to create and remove vault capability", async () => {
// First step: setup switchboard
await shallPass(
Expand Down
6 changes: 6 additions & 0 deletions transactions/create_forwarder.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,11 @@ transaction(receiver: Address) {
ExampleToken.ReceiverPublicPath,
target: /storage/exampleTokenForwarder
)

// Link the new ForwarderPublic capability
acct.link<&{TokenForwarding.ForwarderPublic}>(
/public/exampleTokenForwarder,
target: /storage/exampleTokenForwarder
)
}
}
10 changes: 10 additions & 0 deletions transactions/scripts/switchboard/check_receiver_by_type.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import FungibleTokenSwitchboard from "../../../contracts/FungibleTokenSwitchboard.cdc"
import ExampleToken from "../../../contracts/ExampleToken.cdc"

pub fun main(switchboard: Address): Bool {
let switchboardRef = getAccount(switchboard)
.getCapability<&{FungibleTokenSwitchboard.SwitchboardPublic}>(FungibleTokenSwitchboard.PublicPath)
.borrow()
?? panic("Unable to borrow capability with restricted type of {FungibleTokenSwitchboard.SwitchboardPublic} from ".concat(switchboard.toString()).concat( "account"))
return switchboardRef.checkReceiverByType(type: Type<@ExampleToken.Vault>())
}
10 changes: 10 additions & 0 deletions transactions/scripts/tokenForwarder/is_recipient_valid.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import TokenForwarding from "../../../contracts/utility/TokenForwarding.cdc"

pub fun main(addr: Address, tokenForwardingPath: PublicPath): Bool {
let forwarderRef = getAccount(addr)
.getCapability<&{TokenForwarding.ForwarderPublic}>(tokenForwardingPath)
.borrow()
?? panic("Unable to borrow {TokenForwarding.ForwarderPublic} restrict type from a capability")

return forwarderRef.check()
}
45 changes: 45 additions & 0 deletions transactions/switchboard/safe_transfer_tokens_v2.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import FungibleToken from "./../../contracts/FungibleToken.cdc"
import FungibleTokenSwitchboard from "./../../contracts/FungibleTokenSwitchboard.cdc"
import ExampleToken from "./../../contracts/ExampleToken.cdc"

// This transaction is a template for a transaction that could be used by anyone
// to send tokens to another account through a switchboard using the deposit
// method but before depositing we will explicitly check whether receiving capability is
// borrowable or not and if yes then it will deposit the vault to the receiver capability.
transaction(to: Address, amount: UFix64) {

// The reference to the vault from the payer's account
let vaultRef: &ExampleToken.Vault
// The Vault resource that holds the tokens that are being transferred
let sentVault: @FungibleToken.Vault

prepare(signer: AuthAccount) {

// Get a reference to the signer's stored vault
self.vaultRef = signer.borrow<&ExampleToken.Vault>(from: ExampleToken.VaultStoragePath)
?? panic("Could not borrow reference to the owner's Vault!")

// Withdraw tokens from the signer's stored vault
self.sentVault <-self.vaultRef.withdraw(amount: amount)

}

execute {

// Get the recipient's public account object
let recipient = getAccount(to)

// Get a reference to the recipient's Switchboard Receiver
let switchboardRef = recipient.getCapability(FungibleTokenSwitchboard.PublicPath)
.borrow<&FungibleTokenSwitchboard.Switchboard{FungibleTokenSwitchboard.SwitchboardPublic}>()
?? panic("Could not borrow receiver reference to switchboard!")

// Validate the receiving capability by using safeBorrowByType
if let receivingRef = switchboardRef.safeBorrowByType(type: Type<@ExampleToken.Vault>()){
switchboardRef.deposit(from: <-self.sentVault)
} else {
destroy self.sentVault
}
}

}

0 comments on commit ed4f379

Please sign in to comment.