`msg.sender` isn't the initiator in a contract that's created by another contract

The OpenZeppelin Contracts Wizard generates code that includes msg.sender e.g. for an ERC20 token contract with premint.

image

Then when using this code to deploy it using a CREATE2 or CREATE3 deployer contract like Axelar's Constant Address Deployer, all the tokens go to the ConstAddressDeployer contract instead of the account that called deploy().

Replacing msg.sender with tx.origin in the token contract constructor solves the problem. But I've read in many places that tx.origin shouldn't be used.

What is the best solution?

1 Like

Pass the desired address as an input argument to the contract's constructor.

1 Like

Yes, that would work, I'd have to add constructor parameters.

Though do you see any issue with just using tx.origin in this case?

No, but like you said, it is generally discouraged (including a post by Vitalik himself, stating that it should not be relied on as a valid feature in the language, going forward).

The situation you've described is a common challenge when using factory or deployer contracts in combination with other contracts that rely on msg.sender for initial setup, especially when the desired behavior is to assign ownership or mint tokens to the original caller.

Here's a breakdown of the problem and potential solutions:

  1. The Problem with msg.sender and Factory Contracts:
  • When a contract (like Axelar's Constant Address Deployer) deploys another contract, the msg.sender in the deployed contract's constructor will be the address of the deployer contract, not the address of the account that called the deployer contract's function.
  • This is why, in the scenario you described, all the tokens go to the ConstAddressDeployer contract.
  1. Using tx.origin:
  • tx.origin always refers to the original sender of the transaction, i.e., the externally owned account (EOA) that initiated the transaction.
  • While this would solve the problem in the scenario you described, using tx.origin is generally discouraged because it can introduce security vulnerabilities, especially in more complex dApps where malicious contracts can trick users into sending transactions.
  1. Potential Solutions:
    a. Pass the Desired Address as a Constructor Argument:
  • Modify the token contract to accept an address as a constructor argument. This address will be the recipient of the minted tokens or the owner of the contract.
  • When the deployer contract deploys the token contract, it can pass the desired address (e.g., msg.sender of the deployer contract) as an argument to the token contract's constructor.

b. Use a Setup Function:

  • Instead of setting the owner or minting tokens in the constructor, have a separate public function in the token contract for this purpose. This function can only be called once and sets the owner or mints the tokens.
  • After deploying the token contract using the deployer contract, call this setup function directly from the desired owner account.

c. Delegate Call Initialization:

  • Use a delegate call from the deployer contract to the initialization function of the token contract. This way, msg.sender within the token contract will be the original caller, not the deployer contract. However, this approach is more complex and requires a deeper understanding of the Ethereum Virtual Machine (EVM) to implement safely.
  1. Recommendation:
  • The first solution (passing the desired address as a constructor argument) is the most straightforward and is commonly used in practice. It provides clarity and avoids the pitfalls associated with tx.origin and the complexities of delegate calls.
2 Likes