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

META contract-contract interactions on a single chain #757

Open
moul opened this issue Apr 19, 2023 · 9 comments
Open

META contract-contract interactions on a single chain #757

moul opened this issue Apr 19, 2023 · 9 comments
Labels
📦 🌐 tendermint v2 Issues or PRs tm2 related 📦 🤖 gnovm Issues or PRs gnovm related 🌱 feature New update to Gno

Comments

@moul
Copy link
Member

moul commented Apr 19, 2023

Edit, disclaimer:

Our current objective is not to introduce type-unsafe features for inter-contract calls. In Version 1, we will continue to support the existing native import system and may potentially implement asynchronous methods for sending async messages via IBC or local IBC connections.

For scenarios involving account attraction, contract-based multisigs, and proxy patterns, we should prioritize exploring new solutions that align with Version 1 constraints before attempting to adapt patterns from other ecosystems.


The aim is to provide a summary of the available options for calling contracts, as well as to summarize the investigation of new techniques.

Method Type-Safety Dynamic calls (A)synchronous Status
Import and call (default) Y N Sync ✅ Available
std.{Send,Recv} TBD Y Async 🤔 In consideration
std.Call N Y Sync Won't do for v1
std.Ctx{Set,Get} Y N Sync Won't do for v1

UPDATE: we shouldn't implement .Call or .CtxSet,Get

Current options

Idiomatic go import

The recommended approach involves importing a package or a realm and invoking an exported function, which is highly idiomatic, maintains type-safety, and offers better reliability than a micro-service relying on TCP. Furthermore, this method could enable auto-complete functionality over time.

The proposed solution is limited in that it does not support importing a dynamic package with a variable name.

import "gno.land/r/demo/users"

func usernameOf(addr std.Address) string {
	user := users.GetUserByAddress(addr)
	if user == nil {
		return ""
	}
	return user.Name()
}

Source: https://github.com/gnolang/gno/blob/master/examples/gno.land/r/demo/boards/misc.gno

Potential upcoming solutions

IBC-compatible API

The proposed enhancements include a contract-contract interface using two monodirectional channels to support asynchronous and potentially synchronous calls, with a similar API to the upcoming IBC interface. The channels could also enable external transaction triggering based on chain hooks.

At this stage, it is unclear whether the contract-contract interface would be limited to simple types only, require marshalling, or enable specifying the expected type with simple typed channels.

One of the proposed enhancements involves introducing a new std.Call/Invoke("contract-addr", "method", params...) method."

Pseudo-code:

func Process() {
    // recv
    for _, event := std.Recv() { /* handle */ }

    // send
    std.Send("gno.land/r/demo/foobar", "Method", "arg1", "arg2")
}

Related work:

New std.Call/Invoke method, for dynamic synchronous calls

Warning: user security concerns, losing type-safety

The proposed approach is similar to the IBC one (above), with the exception that it involves a new std.Call helper that is necessarily synchronous.

Pseudo-code:

import "std"

func Foo() {
    ret, err := std.Call("gno.land/r/demo/users", "GetUserByAddress", "g1foobar...")
	println(ret.(string))
}
// or
func Bar() {
    var ret struct{...}
    err := std.Call(&ret, "gno.land/r/demo/users", "GetUserByAddress", "g1foobar...")
}

Related work:

Helpers to extend the calling context

Warning: it should be avoided if we can just update libraries to take a Context argument.

Providing helpers to taint the calling context with metadata, which would enable contracts to be called as before while extending how the dependent contract views its calling graph.

The proposed method for calling contracts has similar limitations to the existing method, in that it is static. However, it adds the capability to simulate various contract-contract interactions, such as specifying that an intermediary contract should be considered as an account and store assets.

Pseudo-code:

import (
    "gno.land/r/demo/foobar"
    "gno.land/p/demo/rules"
)

func VaultExec() {
    // assertIsVaultOwner()
	std.SetContext(rules.PackageAsAccount, true)
	foobar.Transfer(...)
	std.SetContext(rules.PackageAsAccount, false)
}
// or
func VaultExec() {
    // assertIsVaultOwner()
    rules.ExecAsPkg(func() {
        foobar.Transfer(...)
    })
}

Related work:

@moul moul changed the title META contract-contract interactions META contract-contract interactions on a single chain Apr 19, 2023
@moul
Copy link
Member Author

moul commented Apr 19, 2023

Update from gnolang/meetings#5

We need:

  • Encode,Decode (primitive types).
  • To be "IBC-compatible", the arguments and returned values should be in the allowed list.
  • Start slow, then iterate:
    • Support structured arguments (not only primitive types)
    • Support interchain imports
    • Encoding system should be flexible enough to unmarshal to a local type-safe system on another chain

@jaekwon
Copy link
Contributor

jaekwon commented Apr 20, 2023

To clarify the above, we need first to figure out how the "ABI" works for argument and return values. I suggest we just use a slice of stringified primitive values. Later after the prototype we can consider compatibility with ETH ABI, or some other byte form; while also considering how to support structures (non-primitive types).

We don't need Encode or Decode exposed in Gno. From the perspective of Gno it should be transparent how it is encoded/decoded. Maybe what we need is to use DefineNative as in stdlibs/stdlibs.go -- then in there we can stringify the arguments and queue it up. We would need a utility method that panics upon encountering non-primitive values. And later we can figure out how to improve this to support Solidity ABI or something else.

@ajnavarro
Copy link
Contributor

Talking about the API, I like the idiomatic Go import.

Another idea trying to merge a dynamic approach with type safety, we can do something like this:

type myInterface interface {
     Method(arg string) string
}

i, ok := std.GetRealm("gno.land/r/contract/v2").(myInterface)
if !ok {
 [....]
}

out := i.Method("test")

@albttx
Copy link
Member

albttx commented Apr 20, 2023

Here is a list of the std.Get* functions and their use

std.Get*() functions what they do
GetChainID return ChainID
GetHeight return block height
GetOrigSend return Coins sent
GetOrigCaller return user address (tx.orign)
GetOrigPkgAddr return the first calling Realm
GetCallerAt given a frame index, return the caller
GetBanker return a BankerType
GetTimestamp deprecated
CurrentRealmPath return realm path (gno.land/r/example)
std.Get* potential new one What they do
GetCaller/GetSender return the previous Realm calling, if not the user
GetCallersCount (?) return the numbers of callers, currently we can't know GetCallerAt(x) limit
GetPkgAddr return the current PkgAddr

@moul
Copy link
Member Author

moul commented Apr 21, 2023

GetCaller/GetSender | return the previous Realm calling, if not the user

How about GetLastCaller or GetPreviousCaller? Additionally, would a GetRealmAddr function that returns the calling realm or nil be useful?

GetCallersCount (?) | return the numbers of callers, currently we can't know GetCallerAt(x) limit

Options include using GetCallersCount, returning a struct{addr string, index int} instead of a string, or returning nil if overflow occurs.

GetPkgAddr | return the current PkgAddr

👍, This would be useful for reusable contracts with init() functions, as well as for the Render() function when creating links using the [](prefix+path) syntax.

@albttx
Copy link
Member

albttx commented Apr 21, 2023

How about GetLastCaller or GetPreviousCaller?

I understand this point, i'm fine with GetLastCaller but i prefer GetCaller for the reason that a "Caller" already express the sens of "Last" or "previous"

Additionally, would a GetRealmAddr function that returns the calling realm or nil be useful?

i don't this so, people will manage with something like

addr := std.GetRealmAddr()
if addr == nil {
   // handle this
}

So it's the same as

addr := std.GetCaller()
if addr ==  std.GetOrigCaller() {
   // handle this
}

GetPkgAddr | return the current PkgAddr

We need a better definition of the difference between a Realm and a Package. which is clear for us but could be unclear for a Gno realm dev.

Because there is "no need" that a package hold assets. Package "context" should be the realm context

@moul
Copy link
Member Author

moul commented Apr 21, 2023

i don't this so, people will manage with something like...

I was proposing in addition to GetCaller, not to replace it.

Because there is "no need" that a package hold assets. Package "context" should be the realm context

In certain cases, I believe it would be prudent to determine the originating package, allowing for the white-listing of a trustworthy intermediate package, thus enabling its use by anyone.

Realm is a way more common, but both make sense IMO.

@albttx
Copy link
Member

albttx commented Apr 21, 2023

I was proposing in addition to GetCaller, not to replace it.

Yes i understand that, but since it's taking the same amount of LoC, i don't see the usage.

But i could be easy to add if someone express the need of this function :)

[...] allowing for the white-listing of a trustworthy intermediate package,

I love that idea! but what do you think putting this whitelisting inside of gno.mod ?


And do you think we should keep GetCallerAt() ? Do you have an example of when it could be needed ?

@moul
Copy link
Member Author

moul commented May 10, 2023

For those joining late:

Our current objective is not to introduce type-unsafe features for inter-contract calls. In Version 1, we will continue to support the existing native import system and may potentially implement asynchronous methods for sending async messages via IBC or local IBC connections.

For scenarios involving account attraction, contract-based multisigs, and proxy patterns, we should prioritize exploring new solutions that align with Version 1 constraints before attempting to adapt patterns from other ecosystems.

@ajnavarro ajnavarro added 🌱 feature New update to Gno 📦 🤖 gnovm Issues or PRs gnovm related 📦 🌐 tendermint v2 Issues or PRs tm2 related 🌟 improvement performance improvements, refactors ... and removed 🌟 improvement performance improvements, refactors ... labels May 15, 2023
@moul moul added this to the 🌟 main.gno.land (wanted) milestone Sep 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
📦 🌐 tendermint v2 Issues or PRs tm2 related 📦 🤖 gnovm Issues or PRs gnovm related 🌱 feature New update to Gno
Projects
Status: 🌟 Wanted for Launch
Development

No branches or pull requests

5 participants