I am developing smart contracts for a while so I thought this should be doable, but somehow I can't find a solution.
I have a (staking) contract c1 and a (new staking) contract c2.
I want to provide the opportunity for users to migrate staked funds from c1 to c2 with one transaction (from the user's perspective).
User would either call a dedicated migration contract or a migrate function in c2.
Old c1 contract (which is already deployed and immutable) has a withdrawAll() function (with no parameters) which returns all funds back to a user (=msg.sender).
Ideally user would call migrate() in contract c2
c2 would (on behalf of the user) call withdrawAll() in c1
tokens would be send to users wallet
tokens would be staked in new contract c2
The problem is, that when c2 calls withdrawAll() function in c1, the msg.sender will be c2 , not the user's account any more and c1 would refuse the transaction to unstake the funds.
I looked into delegateCall, yes , msg.sender would be set to the user's account when c2 calls withdrawAll() function in c1 , but state variables in c1 could not be updated (which keep track of staked amount of each user).
Altough it would not help in my case, as I can't update the c1 contract, it looks like as if tx.origin could be used (safely ?).
c2 and c1 have the address of the other contract stored, so should be able just to limit access from the other one.
When transferring token and not sending ETH, the attack scenario which is normally mentioned in the context of tx.origin does not apply. In my use case, I am also in control of the token contract which would be called on transfer.
Is there anything which would still put the funds at risk in context of this usage of tx.origin.
Aren't there useful and safe usage scenarios or is the usage of tx.origin in any case dangerous ?
It doesn't matter. It a authorization checking problem. tx.origin is set to the original EOA account that sent the transaction, while msg.sender is set to the caller contract/EOA.
Say account A interacts with contract X, which call a method on Y, which calls a method on Z. all contracts will have tx.originset to A. this means that a malicious actor can write a contract that interacts with X and convince a user to interact with that malicious contract.
Thanks for the additional explanantion.
Yes, right. Contract Z would not be able to see if there is a man-in-the-middle-attack.
The only idea I can think of atm would be that all contracts would need some additional access control logic. In my example ...
c1 holds staked user's (A) funds
user can directly call c1 to withdraw funds
However, user can also call c2, which would in turn calls c1, (A -> c2 -> c1) , but c1 one would need to know c2's address so it can restrict calls to it's function to c2 (and user) only.
c2 would also need to forward original msg.sender (= user account) to c1
Does not look ideal to me, but should be doable, however not in my case as I can not change the code of c1 any more (which is just checking msg.sender).
Really surprised that there is no nicer solution and also that I never stumbled across the requirement that a contract would just need to call two other functions in other contract(s) on behalf of the user.
I thought "worst case" it would be similar to a proxy, but what I have seen in OZ proxies, only the logic of the called contract would be used (with delegateCall), but the called contract state can not be changed.