It seems like currently it is not possible to overwrite the signer of a transaction, including function call. Indeed, contract.function({from: someoneElseThanDeployer}) will return transaction cannot override from as an error. Perhaps I am misunderstanding something?
If this is indeed not possible, I would argue that this is inconvenient for writing tests. Using various addresses to interact with the same contract is often necessary, such as with a contract with an owner. It is therefore necessary for proper testing to allow multiple transaction signer for a same contract.
Are you trying to override the from in a send transaction? That is certainly not possible, as a transaction does not include a from; it is implicitly calculated from the signature of a signed transaction.
If you need a contract from the perspective of multiple accounts, you could do something like:
let contract = // Something here
// Ganache or local Geth node with accounts
let provider = new ethers.providers.JsonRpcProvider();
// Get the signers; accounts 0 through 2
let signer0 = provider.getSigner(0);
let signer1 = provider.getSigner(1);
let signer2 = provider.getSigner(2);
// contract.prototype.connect allows you to change who the contract is run as
let contractReadOnly = contract.connect(provider);
let contractAsSigner0 = contract.connect(signer0);
let contractAsSigner1 = contract.connect(signer1);
let contractAsSigner2 = contract.connect(signer2);
// Testing something that should work...
contractAsSigner0.someFunctionOnlySigner0CanDo().then((tx) => {
return tx.wait().then((receipt) => {
assert.ok(true);
}, (error) => {
console.log("This should not have been an error!");
assert.ok(false);
});
});
// Testing something that should fail....
// NOTE: signer1 calling a function only signer0 may call
contractAsSigner1.someFunctionOnlySigner0CanDo().then((tx) => {
return tx.wait().then((receipt) => {
console.log("This should have failed!!");
assert.ok(false);
}, (error) => {
assert.ok(true);
});
});
A contract must always have a frame-of-reference, a who the contract is connected as, so that there is someone to sign a transaction. Or if it is a provider, not a signer, the contract can know that it cannot sign anything, but only has read-only access.
In Web3, things like your example work because a contract is just a simple proxy to the under-lying connection, so the from can always ask any account on the node to sign. This works as long as you are connected to a node (like Geth or Parity), but fails if you are, for example, using INFURA or Etherscan, which don't have accounts. The use of an account must be explicit in ethers.js, since many different types of Signers (JsonRpcSigner, Wallet, LedgerSigner, etc) and different types of Providers (Etherscan, INFURA, JsonRpcProvider, etc) are supported. As a result, many things must be lowest-common-denominator, but also as a result, it is easy to swap one sub-system out for another.
This is something I need to document, which is the separation between a Provider and a Signer. In Web3.js apps, there is generally no distinction. In ethers, there are 3 possible cases:
I'm working on the v4 documentation now, and once it is ready, will publish the production release of v4. :)
The above example is using the v4 API, if you need the v3 api, you can call provider.listAccounts() and create the signers using the addresses, for example:
provider.listAccounts().then((addresses) => {
let signer0 = provider.getSigner(addresses[0]);
let signer1 = provider.getSigner(addresses[1]);
let signer2 = provider.getSigner(addresses[2]);
});
This is a great explanation! Thanks a lot! I was suspecting it was a limitation due to Ethers design. Happy to see the connect method exists :).
I'm going to close this issue now. If you have further issues, please feel free to re-open.
Thanks! :)
Most helpful comment
Are you trying to override the
fromin a send transaction? That is certainly not possible, as a transaction does not include a from; it is implicitly calculated from the signature of a signed transaction.If you need a contract from the perspective of multiple accounts, you could do something like:
A contract must always have a frame-of-reference, a who the contract is connected as, so that there is someone to sign a transaction. Or if it is a provider, not a signer, the contract can know that it cannot sign anything, but only has read-only access.
In Web3, things like your example work because a contract is just a simple proxy to the under-lying connection, so the
fromcan always ask any account on the node to sign. This works as long as you are connected to a node (like Geth or Parity), but fails if you are, for example, using INFURA or Etherscan, which don't have accounts. The use of an account must be explicit in ethers.js, since many different types of Signers (JsonRpcSigner, Wallet, LedgerSigner, etc) and different types of Providers (Etherscan, INFURA, JsonRpcProvider, etc) are supported. As a result, many things must be lowest-common-denominator, but also as a result, it is easy to swap one sub-system out for another.This is something I need to document, which is the separation between a
Providerand aSigner. In Web3.js apps, there is generally no distinction. In ethers, there are 3 possible cases:I'm working on the v4 documentation now, and once it is ready, will publish the production release of v4. :)
The above example is using the v4 API, if you need the v3 api, you can call provider.listAccounts() and create the signers using the addresses, for example: