Skip to content

Commit

Permalink
invalid transfer token and forwarding (#24)
Browse files Browse the repository at this point in the history
* invalid transfer token

* fix forwarding contract and tests

* assets
  • Loading branch information
joshuahannan authored Jun 19, 2020
1 parent dde8b46 commit 64c9531
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 70 deletions.
8 changes: 7 additions & 1 deletion contracts/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func ExampleToken(fungibleTokenAddr string) []byte {
// CustomToken returns the ExampleToken contract with a custom name.
//
// The returned contract will import the FungibleToken interface from the specified address.
func CustomToken(fungibleTokenAddr, tokenName, storageName string) []byte {
func CustomToken(fungibleTokenAddr, tokenName, storageName, initialBalance string) []byte {
code := assets.MustAssetString(exampleTokenFilename)

code = strings.ReplaceAll(
Expand All @@ -75,6 +75,12 @@ func CustomToken(fungibleTokenAddr, tokenName, storageName string) []byte {
storageName,
)

code = strings.ReplaceAll(
code,
"1000.0",
initialBalance,
)

return []byte(code)
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/contracts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func TestExampleTokenContract(t *testing.T) {
}

func TestCustomExampleTokenContract(t *testing.T) {
contract := contracts.CustomToken(addrA.Hex(), "UtilityCoin", "utilityCoin")
contract := contracts.CustomToken(addrA.Hex(), "UtilityCoin", "utilityCoin", "100.0")
assert.NotNil(t, contract)
assert.Contains(t, string(contract), addrA.Hex())
}
Expand Down
12 changes: 6 additions & 6 deletions contracts/internal/assets/assets.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/contracts/ExampleToken.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ pub contract ExampleToken: FungibleToken {
// Create a public capability to the stored Vault that only exposes
// the `deposit` method through the `Receiver` interface
//
self.account.link<&ExampleToken.Vault{FungibleToken.Receiver}>(
self.account.link<&{FungibleToken.Receiver}>(
/public/exampleTokenReceiver,
target: /storage/exampleTokenVault
)
Expand Down
23 changes: 17 additions & 6 deletions src/contracts/TokenForwarding.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -26,32 +26,43 @@ pub contract TokenForwarding {
// This is where the deposited tokens will be sent.
// The type indicates that it is a reference to a receiver
//
access(self) var recipient: &{FungibleToken.Receiver}
access(self) var recipient: Capability

// deposit
//
// Function that takes a Vault object as an argument and forwards
// it to the recipient's Vault using the stored reference
//
pub fun deposit(from: @FungibleToken.Vault) {
emit ForwardedDeposit(amount: from.balance, from: self.owner?.address)
self.recipient.deposit(from: <-from)
let receiverRef = self.recipient.borrow<&{FungibleToken.Receiver}>()!

let balance = from.balance

receiverRef.deposit(from: <-from)

emit ForwardedDeposit(amount: balance, from: self.owner?.address)
}

// changeRecipient changes the recipient of the forwarder to the provided recipient
//
pub fun changeRecipient(_ newRecipient: &{FungibleToken.Receiver}) {
pub fun changeRecipient(_ newRecipient: Capability) {
pre {
newRecipient.borrow<&{FungibleToken.Receiver}>() != nil: "Could not borrow Receiver reference from the Capability"
}
self.recipient = newRecipient
}

init(recipient: &{FungibleToken.Receiver}) {
init(recipient: Capability) {
pre {
recipient.borrow<&{FungibleToken.Receiver}>() != 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: &{FungibleToken.Receiver}): @Forwarder {
pub fun createNewForwarder(recipient: Capability): @Forwarder {
return <-create Forwarder(recipient: recipient)
}
}
96 changes: 84 additions & 12 deletions test/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import (
// a new Vault instance and stores it in storage.
// balance is an argument to the Vault constructor.
// The Vault must have been deployed already.
func GenerateCreateTokenScript(fungibleAddr, tokenAddr flow.Address, tokenName, storageName string) []byte {
func GenerateCreateTokenScript(fungibleAddr, tokenAddr flow.Address, tokenName string) []byte {
storageName := MakeFirstLowerCase(tokenName)

template := `
import FungibleToken from 0x%[1]s
import %[3]s from 0x%[2]s
Expand All @@ -21,7 +23,7 @@ func GenerateCreateTokenScript(fungibleAddr, tokenAddr flow.Address, tokenName,
let vault <- %[3]s.createEmptyVault()
acct.save(<-vault, to: /storage/%[4]sVault)
acct.link<&%[3]s.Vault{FungibleToken.Receiver}>(/public/%[4]sReceiver, target: /storage/%[4]sVault)
acct.link<&{FungibleToken.Receiver}>(/public/%[4]sReceiver, target: /storage/%[4]sVault)
acct.link<&%[3]s.Vault{FungibleToken.Balance}>(/public/%[4]sBalance, target: /storage/%[4]sVault)
}
}
Expand All @@ -31,7 +33,9 @@ func GenerateCreateTokenScript(fungibleAddr, tokenAddr flow.Address, tokenName,

// GenerateDestroyVaultScript creates a script that withdraws
// tokens from a vault and destroys the tokens
func GenerateDestroyVaultScript(fungibleAddr, tokenAddr flow.Address, tokenName, storageName string, withdrawAmount int) []byte {
func GenerateDestroyVaultScript(fungibleAddr, tokenAddr flow.Address, tokenName string, withdrawAmount int) []byte {
storageName := MakeFirstLowerCase(tokenName)

template := `
import FungibleToken from 0x%[1]s
import %[3]s from 0x%[2]s
Expand All @@ -55,7 +59,9 @@ func GenerateDestroyVaultScript(fungibleAddr, tokenAddr flow.Address, tokenName,

// GenerateTransferVaultScript creates a script that withdraws an tokens from an account
// and deposits it to another account's vault
func GenerateTransferVaultScript(fungibleAddr, tokenAddr flow.Address, receiverAddr flow.Address, tokenName, storageName string, amount int) []byte {
func GenerateTransferVaultScript(fungibleAddr, tokenAddr flow.Address, receiverAddr flow.Address, tokenName string, amount int) []byte {
storageName := MakeFirstLowerCase(tokenName)

template := `
import FungibleToken from 0x%s
import %s from 0x%s
Expand All @@ -64,10 +70,10 @@ func GenerateTransferVaultScript(fungibleAddr, tokenAddr flow.Address, receiverA
prepare(acct: AuthAccount) {
let recipient = getAccount(0x%s)
let providerRef = acct.borrow<&%s.Vault{FungibleToken.Provider}>(from: /storage/%sVault)
let providerRef = acct.borrow<&{FungibleToken.Provider}>(from: /storage/%sVault)
?? panic("Could not borrow Provider reference to the Vault!")
let receiverRef = recipient.getCapability(/public/%sReceiver)!.borrow<&%s.Vault{FungibleToken.Receiver}>()
let receiverRef = recipient.getCapability(/public/%sReceiver)!.borrow<&{FungibleToken.Receiver}>()
?? panic("Could not borrow receiver reference to the recipient's Vault")
let tokens <- providerRef.withdraw(amount: %d.0)
Expand All @@ -77,19 +83,21 @@ func GenerateTransferVaultScript(fungibleAddr, tokenAddr flow.Address, receiverA
}
`

return []byte(fmt.Sprintf(template, fungibleAddr, tokenName, tokenAddr, receiverAddr, tokenName, storageName, storageName, tokenName, amount))
return []byte(fmt.Sprintf(template, fungibleAddr, tokenName, tokenAddr, receiverAddr, storageName, storageName, amount))
}

// GenerateMintTokensScript creates a script that uses the admin resource
// to mint new tokens and deposit them in a Vault
func GenerateMintTokensScript(fungibleAddr, tokenAddr flow.Address, receiverAddr flow.Address, tokenName, storageName string, amount float64) []byte {
func GenerateMintTokensScript(fungibleAddr, tokenAddr flow.Address, receiverAddr flow.Address, tokenName string, amount float64) []byte {
storageName := MakeFirstLowerCase(tokenName)

template := `
import FungibleToken from 0x%[1]s
import %[3]s from 0x%[2]s
transaction {
let tokenAdmin: &%[3]s.Administrator
let tokenReceiver: &%[3]s.Vault{FungibleToken.Receiver}
let tokenReceiver: &{FungibleToken.Receiver}
prepare(signer: AuthAccount) {
self.tokenAdmin = signer
Expand All @@ -98,7 +106,7 @@ func GenerateMintTokensScript(fungibleAddr, tokenAddr flow.Address, receiverAddr
self.tokenReceiver = getAccount(0x%[5]s)
.getCapability(/public/%[4]sReceiver)!
.borrow<&%[3]s.Vault{FungibleToken.Receiver}>()
.borrow<&{FungibleToken.Receiver}>()
?? panic("Unable to borrow receiver reference")
}
Expand All @@ -118,7 +126,9 @@ func GenerateMintTokensScript(fungibleAddr, tokenAddr flow.Address, receiverAddr

// GenerateBurnTokensScript creates a script that uses the admin resource
// to destroy tokens and deposit them in a Vault
func GenerateBurnTokensScript(fungibleAddr, tokenAddr flow.Address, tokenName, storageName string, amount int) []byte {
func GenerateBurnTokensScript(fungibleAddr, tokenAddr flow.Address, tokenName string, amount int) []byte {
storageName := MakeFirstLowerCase(tokenName)

template := `
import FungibleToken from 0x%[1]s
import %[3]s from 0x%[2]s
Expand Down Expand Up @@ -154,10 +164,44 @@ func GenerateBurnTokensScript(fungibleAddr, tokenAddr flow.Address, tokenName, s
return []byte(fmt.Sprintf(template, fungibleAddr, tokenAddr, tokenName, storageName, amount))
}

// GenerateTransferInvalidVaultScript creates a script that withdraws an tokens from an account
// and tries to deposit it into a vault of the wrong type. Should fail
func GenerateTransferInvalidVaultScript(fungibleAddr, tokenAddr, otherTokenAddr, receiverAddr flow.Address, tokenName, otherTokenName string, amount int) []byte {
storageName := MakeFirstLowerCase(tokenName)

otherStorageName := MakeFirstLowerCase(tokenName)

template := `
import FungibleToken from 0x%s
import %s from 0x%s
import %s from 0x%s
transaction {
prepare(acct: AuthAccount) {
let recipient = getAccount(0x%s)
let providerRef = acct.borrow<&{FungibleToken.Provider}>(from: /storage/%sVault)
?? panic("Could not borrow Provider reference to the Vault!")
let receiverRef = recipient.getCapability(/public/%sReceiver)!.borrow<&{FungibleToken.Receiver}>()
?? panic("Could not borrow receiver reference to the recipient's Vault")
let tokens <- providerRef.withdraw(amount: %d.0)
receiverRef.deposit(from: <-tokens)
}
}
`

return []byte(fmt.Sprintf(template, fungibleAddr, tokenName, tokenAddr, otherTokenName, otherTokenAddr, receiverAddr, storageName, otherStorageName, amount))
}

// GenerateInspectVaultScript creates a script that retrieves a
// Vault from the array in storage and makes assertions about
// its balance. If these assertions fail, the script panics.
func GenerateInspectVaultScript(fungibleAddr, tokenAddr, userAddr flow.Address, tokenName, storageName string, expectedBalance float64) []byte {
func GenerateInspectVaultScript(fungibleAddr, tokenAddr, userAddr flow.Address, tokenName string, expectedBalance float64) []byte {
storageName := MakeFirstLowerCase(tokenName)

template := `
import FungibleToken from 0x%[1]s
import %[3]s from 0x%[2]s
Expand All @@ -180,6 +224,7 @@ func GenerateInspectVaultScript(fungibleAddr, tokenAddr, userAddr flow.Address,
// the total supply of tokens in existence
// and makes assertions about the number
func GenerateInspectSupplyScript(fungibleAddr, tokenAddr flow.Address, tokenName string, expectedSupply int) []byte {

template := `
import FungibleToken from 0x%[1]s
import %[3]s from 0x%[2]s
Expand All @@ -194,3 +239,30 @@ func GenerateInspectSupplyScript(fungibleAddr, tokenAddr flow.Address, tokenName

return []byte(fmt.Sprintf(template, fungibleAddr, tokenAddr, tokenName, expectedSupply))
}

// GenerateCreateForwarderScript creates a script that instantiates
// a new forwarder instance in an account
func GenerateCreateForwarderScript(fungibleAddr, forwardingAddr, receiverAddr flow.Address, tokenName string) []byte {
storageName := MakeFirstLowerCase(tokenName)

template := `
import FungibleToken from 0x%[1]s
import TokenForwarding from 0x%[2]s
transaction {
prepare(acct: AuthAccount) {
let recipient = getAccount(0x%[4]s).getCapability(/public/%[3]sReceiver)!
let vault <- TokenForwarding.createNewForwarder(recipient: recipient)
acct.save(<-vault, to: /storage/%[3]sForwarder)
if acct.getCapability(/public/%[3]sReceiver)!.borrow<&{FungibleToken.Receiver}>() != nil {
acct.unlink(/public/%[3]sReceiver)
}
acct.link<&{FungibleToken.Receiver}>(/public/%[3]sReceiver, target: /storage/%[3]sForwarder)
}
}
`
return []byte(fmt.Sprintf(template, fungibleAddr, forwardingAddr, storageName, receiverAddr))
}
17 changes: 17 additions & 0 deletions test/test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package test

import (
"bytes"
"io/ioutil"
"strings"
"testing"

"github.com/onflow/flow-go-sdk/crypto"
Expand Down Expand Up @@ -99,3 +101,18 @@ func readFile(path string) []byte {
}
return contents
}

// MakeFirstLowerCase makes the first letter in a string lowercase
func MakeFirstLowerCase(s string) string {

if len(s) < 2 {
return strings.ToLower(s)
}

bts := []byte(s)

lc := bytes.ToLower([]byte{bts[0]})
rest := bts[1:]

return string(bytes.Join([][]byte{lc, rest}, nil))
}
Loading

0 comments on commit 64c9531

Please sign in to comment.