Skip to content
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

Implement check functionality in AuthAccount #2492

Merged
merged 7 commits into from
May 29, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions runtime/interpreter/interpreter.go
Original file line number Diff line number Diff line change
@@ -4018,6 +4018,50 @@ func (interpreter *Interpreter) authAccountBorrowFunction(addressValue AddressVa
)
}

func (interpreter *Interpreter) authAccountCheckFunction(addressValue AddressValue) *HostFunctionValue {

// Converted addresses can be cached and don't have to be recomputed on each function invocation
address := addressValue.ToAddress()

return NewHostFunctionValue(
interpreter,
sema.AuthAccountTypeCheckFunctionType,
func(invocation Invocation) Value {
interpreter := invocation.Interpreter

path, ok := invocation.Arguments[0].(PathValue)
if !ok {
panic(errors.NewUnreachableError())
}

domain := path.Domain.Identifier()
identifier := path.Identifier

storageMapKey := StringStorageMapKey(identifier)

value := interpreter.ReadStored(address, domain, storageMapKey)

if value == nil {
return FalseValue
}

// If there is value stored for the given path,
// check that it satisfies the type given as the type argument.

typeParameterPair := invocation.TypeParameterTypes.Oldest()
if typeParameterPair == nil {
panic(errors.NewUnreachableError())
}

ty := typeParameterPair.Value

valueStaticType := value.StaticType(interpreter)

return AsBoolValue(interpreter.IsSubTypeOfSemaType(valueStaticType, ty))
},
)
}

func (interpreter *Interpreter) authAccountLinkFunction(addressValue AddressValue) *HostFunctionValue {

// Converted addresses can be cached and don't have to be recomputed on each function invocation
7 changes: 7 additions & 0 deletions runtime/interpreter/value_account.go
Original file line number Diff line number Diff line change
@@ -72,6 +72,7 @@ func NewAuthAccountValue(
var copyFunction *HostFunctionValue
var saveFunction *HostFunctionValue
var borrowFunction *HostFunctionValue
var checkFunction *HostFunctionValue
var linkFunction *HostFunctionValue
var linkAccountFunction *HostFunctionValue
var unlinkFunction *HostFunctionValue
@@ -188,6 +189,12 @@ func NewAuthAccountValue(
}
return borrowFunction

case sema.AuthAccountTypeCheckFunctionName:
if checkFunction == nil {
checkFunction = inter.authAccountCheckFunction(address)
}
return checkFunction

case sema.AuthAccountTypeLinkFunctionName:
if linkFunction == nil {
linkFunction = inter.authAccountLinkFunction(address)
8 changes: 8 additions & 0 deletions runtime/sema/authaccount.cdc
Original file line number Diff line number Diff line change
@@ -109,6 +109,14 @@ pub struct AuthAccount {
/// The path must be a storage path, i.e., only the domain `storage` is allowed
pub fun borrow<T: &Any>(from: StoragePath): T?

/// Returns true if the object in account storage under the given path satisfies the given type,
/// i.e. could be borrowed using the given type.
///
/// The given type must not necessarily be exactly the same as the type of the borrowed object.
///
/// The path must be a storage path, i.e., only the domain `storage` is allowed.
pub fun check<T: Any>(from: StoragePath): Bool

/// **DEPRECATED**: Instead, use `capabilities.storage.issue`, and `capabilities.publish` if the path is public.
///
/// Creates a capability at the given public or private path,
38 changes: 38 additions & 0 deletions runtime/sema/authaccount.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

93 changes: 86 additions & 7 deletions runtime/tests/interpreter/account_test.go
Original file line number Diff line number Diff line change
@@ -644,6 +644,10 @@ func TestInterpretAuthAccount_borrow(t *testing.T) {
account.save(<-r, to: /storage/r)
}

fun checkR(): Bool {
return account.check<@R>(from: /storage/r)
}

fun borrowR(): &R? {
return account.borrow<&R>(from: /storage/r)
}
@@ -652,10 +656,18 @@ func TestInterpretAuthAccount_borrow(t *testing.T) {
return account.borrow<&R>(from: /storage/r)!.foo
}

fun checkR2(): Bool {
return account.check<@R2>(from: /storage/r)
}

fun borrowR2(): &R2? {
return account.borrow<&R2>(from: /storage/r)
}

fun checkR2WithInvalidPath(): Bool {
return account.check<@R2>(from: /storage/wrongpath)
}

fun changeAfterBorrow(): Int {
let ref = account.borrow<&R>(from: /storage/r)!

@@ -680,7 +692,15 @@ func TestInterpretAuthAccount_borrow(t *testing.T) {

t.Run("borrow R ", func(t *testing.T) {

// first borrow
// first check & borrow
checkRes, err := inter.Invoke("checkR")
require.NoError(t, err)
AssertValuesEqual(
t,
inter,
interpreter.AsBoolValue(true),
checkRes,
)

value, err := inter.Invoke("borrowR")
require.NoError(t, err)
@@ -711,7 +731,15 @@ func TestInterpretAuthAccount_borrow(t *testing.T) {

// TODO: should fail, i.e. return nil

// second borrow
// second check & borrow
checkRes, err = inter.Invoke("checkR")
require.NoError(t, err)
AssertValuesEqual(
t,
inter,
interpreter.AsBoolValue(true),
checkRes,
)

value, err = inter.Invoke("borrowR")
require.NoError(t, err)
@@ -727,8 +755,16 @@ func TestInterpretAuthAccount_borrow(t *testing.T) {
})

t.Run("borrow R2", func(t *testing.T) {
checkRes, err := inter.Invoke("checkR2")
require.NoError(t, err)
AssertValuesEqual(
t,
inter,
interpreter.AsBoolValue(false),
checkRes,
)

_, err := inter.Invoke("borrowR2")
_, err = inter.Invoke("borrowR2")
RequireError(t, err)

require.ErrorAs(t, err, &interpreter.ForceCastTypeMismatchError{})
@@ -744,6 +780,17 @@ func TestInterpretAuthAccount_borrow(t *testing.T) {

require.ErrorAs(t, err, &interpreter.DereferenceError{})
})

t.Run("check R2 with wrong path", func(t *testing.T) {
checkRes, err := inter.Invoke("checkR2WithInvalidPath")
require.NoError(t, err)
AssertValuesEqual(
t,
inter,
interpreter.AsBoolValue(false),
checkRes,
)
})
})

t.Run("struct", func(t *testing.T) {
@@ -778,15 +825,23 @@ func TestInterpretAuthAccount_borrow(t *testing.T) {
account.save(s, to: /storage/s)
}

fun checkS(): Bool {
return account.check<S>(from: /storage/s)
}

fun borrowS(): &S? {
return account.borrow<&S>(from: /storage/s)
}

fun foo(): Int {
return account.borrow<&S>(from: /storage/s)!.foo
}

fun borrowS2(): &S2? {

fun checkS2(): Bool {
return account.check<S2>(from: /storage/s)
}

fun borrowS2(): &S2? {
return account.borrow<&S2>(from: /storage/s)
}

@@ -821,7 +876,15 @@ func TestInterpretAuthAccount_borrow(t *testing.T) {

t.Run("borrow S", func(t *testing.T) {

// first borrow
// first check & borrow
checkRes, err := inter.Invoke("checkS")
require.NoError(t, err)
AssertValuesEqual(
t,
inter,
interpreter.AsBoolValue(true),
checkRes,
)

value, err := inter.Invoke("borrowS")
require.NoError(t, err)
@@ -852,7 +915,15 @@ func TestInterpretAuthAccount_borrow(t *testing.T) {

// TODO: should fail, i.e. return nil

// second borrow
// second check & borrow
checkRes, err = inter.Invoke("checkS")
require.NoError(t, err)
AssertValuesEqual(
t,
inter,
interpreter.AsBoolValue(true),
checkRes,
)

value, err = inter.Invoke("borrowS")
require.NoError(t, err)
@@ -868,6 +939,14 @@ func TestInterpretAuthAccount_borrow(t *testing.T) {
})

t.Run("borrow S2", func(t *testing.T) {
checkRes, err := inter.Invoke("checkS2")
require.NoError(t, err)
AssertValuesEqual(
t,
inter,
interpreter.AsBoolValue(false),
checkRes,
)

_, err = inter.Invoke("borrowS2")
RequireError(t, err)