#Auth-Next for Contracts

36 messages · Page 1 of 1 (latest)

fleet dune
#

Creating a thread to track a potential footgun for cross contract calls with the new auth-next pattern.

Discussion from soroban-chat:
mootz12: Has anyone tested auth-next from the perspective of contracts much?

Curious if any1 knows the expected behavior of require_auth given the following situation:

ContractA calls ContractB, that contains a function doing the following:

fn contract_b_function(contractA: Address) {
    contractA.require_auth(); // I expect this to pass
    // .... some cool contract logic
    let token_client = // some token client
    token_client.xfer(contractA, env.current_contract_address(), 123);
    // ^ would the `require_auth` call for contractA pass in the token contract?
}

[11:57 AM]tdep: I haven't tested it but I don't see why it wouldn't work (after all, it does work with stellar accounts). I think that the footprint (are contract auths generated upon simulation like for stellar accounts? thinking about this it would seem not as it's an intrinsic property of a contract to call certain functions, while it isn't for stellar accounts so they need to generate and sign the auth) comes into play here as it would tell to generate auth for both contract_b_function() and xfer().

[11:59 AM]tdep: ^^ but that's just an assumption

[12:07 PM]mootz12: (I think it works this way, but the WASM integration testing might not be fully accurate for this, cause I can submit transactions through any Address without generating signatures)

If feels like this is a requirement to work for consistencies sake. That being said, this is different than Ethereum in terms of an authentication pattern. msg.sender is only the previous invoker (IE require-auth for the token would fail in this example cause contractB is the "sender")

[12:08 PM]tdep: This would mean that a contract A that exposes a function like the following where for example the contract accepts another contract to invoke as argument without further checks:

fn ctr_f(e: Env, to_invoke: ByteN<32>) {
  e.invoke_contract(&e, &to_invoke, (..., e.current_contract_address()).into());
}

would be exploitable by to_invoke since it could simply just pass the contract A address provided as param to a token contract (or even to whitelisted methods). Which is something that wouldn't work on ethereum with msg.sender

[12:10 PM]mootz12: Yeah, if this does work the calling contract would need to be incredibly careful about what they call, cause they are basically signing anything.

tired sparrow
#

To answer:

mootz12: (I think it works this way, but the WASM integration testing might not be fully accurate for this, cause I can submit transactions through any Address without generating signatures)

If feels like this is a requirement to work for consistencies sake. That being said, this is different than Ethereum in terms of an authentication pattern. msg.sender is only the previous invoker (IE require-auth for the token would fail in this example cause contractB is the "sender")

I can assert that for what regards stellar accounts it does work (https://github.com/xycloo/xycloans/blob/main/flash-loan-vault/src/contract.rs#L71) on futurenet, but with contract auths it might be different since you don't need to create and then sign the auth from the invocation

covert sigil
#

basically a contract invoker only authorizes the direct call. there is currently no way to make e.g. an xfer call on its behalf, which is I agree inconsistent with how external accounts work (but at least safe). the issue above is to address this inconsistency

#

to be clear, there was close to no demand for that so far, which is why it hasn't been done yet

tired sparrow
#

makes sense, for #744 I think that the second one is the best choice since it allows to explicitly authorize the additional authorizations

covert sigil
#

(that's because with cross-contract calls you have much more freedom and can avoid passing the auth through)

#

yeah, both options in #744 are about explicitly authorizing calls on behalf of the current contract. I'm not confident yet which one is better implementation-wise (SDK will probably provide interface like in option 2)

tired sparrow
covert sigil
#

no, this definitely shouldn't happen automatically. basically you'd call something like env.authorize_as_current_contract(...) and next call would use this authorization. the contents of ... is a bit tricky part though, which is why it's not there yet (again, this doesn't seem like a super-critical feature to me)

tired sparrow
#

yeah I also think this isn't a critical requirement

fleet dune
#

I somewhat disagree that this isn't important, as it appears to break account abstraction with auth-next.

If protocols want contracts to be able to call their functions, (consider a smart wallet), they can't use auth-next to authenticate actions, and need to rely on techniques like approvals.

covert sigil
#

well, if you have some good use cases, then it would be useful to e.g. add them to the issue. I'm not saying we won't do that, just that until now there wasn't any real demand for that

fleet dune
#

I can some context to the issue

covert sigil
#

also, to be clear, this is a bit orthogonal to account abstraction as 'call tree signing' is a separate feature.

fleet dune
#

This is true, but I'm worried contracts won't be able to treat incoming callers as an "account", rather they will need options for "external key?" and "contract", but I can give a concrete example on the issue

tired sparrow
fleet dune
tired sparrow
tired sparrow
covert sigil
#

ok, that sounds good

#

FWIW the 'smart wallet' == custom account contract and it probably shouldn't need to call another contracts directly. but the general argument still makes sense

fleet dune
covert sigil
#

I want to get it into preview 10, so unless there is some serious implementation issue, we should be able to get it

dense sleet
#

I was under the impression that it is possible for contracts to do auth if they implement __check_auth and allow auths for themselves to go through if they are in the call history?

covert sigil
#

__check_auth verifies the external signatures. the use case here is for the cases when the contract is invoker

#

(and there are no signatures at all, of course)

dense sleet
#

__check_auth can verify anything we want though right? The signatures can be blank/empty. And the function can be coded to succeed based off the invocation call tree?

#

A better solution is welcome, I just thought __check_auth was flexible enough to be usable in this situation.

covert sigil
#

it would still require passing ContractAuth entries in the transaction which seems redundant (contract invoker auth is currently transparent, which makes sense given that this is basically an implementation detail)

#

also you'd need to enable reentrance

#

so seems like a lot of work for the contract devs and even more footguns

dense sleet
#

Once #744 is implemented, do we need __check_auth anymore? 🤔

covert sigil
#

yes, unless we want to drop support for more than 1 authorizer address per contract

#

this would allow eth style 'account abstraction' where we basically can implement invoker auth with arbitrary authentication implementation