truffle icon indicating copy to clipboard operation
truffle copied to clipboard

Add feature to deploy a contract at a specific address

Open leiiiooo opened this issue 2 years ago • 14 comments

I am developing a contract, which can be easily developed using Truffle Suite. But I am having a problem when I am writing unit tests. Here is the pseudo-code of the function.

function(bytes calldata signature) external {
      bytes32 hash = keccak256(
            abi.encodePacked(
                a,
                b,
                c,
                address(this)
            )
        );
        address recovered = ECDSA.recover(hash, signature);
}

But then I run into a problem, I can't know the address of the contract in advance when I generate the signature, the address of the contract will be different for different people, on different computers when I run the unit test.

I think we need such a feature to facilitate writing unit tests.

For example.

MyContract.fixedAddress(myAddress);

The address we get when we call address(this) inside the contract is the address we configured. It is also convenient to use Java code or other languages to generate this signature. 🤔

leiiiooo avatar May 25 '22 11:05 leiiiooo

@leiiiooo thanks for submitting this issue. Are you looking for a way to find out the deployment address of the contract in advance or are you looking to control a deployment address? Please specify more precisely what you intend to be able to do.

dongmingh avatar May 26 '22 18:05 dongmingh

Thank you for your reply. I am looking for a way to control a deployment address because as shown in the code above, I need to know the deployment address of this contract in advance because I need to calculate this signature in java code and I need to run this part of the logic check in unit tests, so it would be better to provide a mock method, for example using Truffle. mockContractAddress(myAddress). That way this contract address is known and controllable even if I allow as many unit tests as I want.

leiiiooo avatar May 31 '22 01:05 leiiiooo

Hey @leiiiooo, you can determine the address at which a contract will be deployed if you know the address and nonce of the account that is sending the contract creation transaction. See https://ethereum.stackexchange.com/a/761/44640 for how to do this

As an alternative, as of Ganache 7.0.3 you can use a new RPC method, evm_setAccountCode, to set the contract code at a specific address. See https://github.com/trufflesuite/ganache/releases/tag/v7.0.3#user-content-7.0.3-new-features for details.

For now, we'll leave this feature request issue open in order to gauge community interest in the idea.

Let us know if you have any other questions.

davidmurdoch avatar Jun 02 '22 18:06 davidmurdoch

@davidmurdoch thank you very much for your reply, I think this proposal (https://github.com/trufflesuite/ganache/pull/2337) is very interesting and necessary to make it very easy to write some unit test code. I will try to use Ganache v7.0.3. Thanks again for your reply.

leiiiooo avatar Jun 05 '22 05:06 leiiiooo

Here is my test code, I am trying to understand the meaning of Contract.at() function. As you can see, I have insert the code to mockAddress, but when I call the Hello.at(mockAddress) function, it throws exception Error: Cannot create instance of Hello; no code at address 0xe2d3A739EFFCd3A99387d015E260eEFAc72EBea1. Feel really strange. Any idea, where am I wrong?

const Hello = artifacts.require("hello/Hello");
contract("Hello test code", async function () {
  it("Contract.at", async function () {
    const mockAddress = "0xe2d3A739EFFCd3A99387d015E260eEFAc72EBea1";
    const code =
      "0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea264697066735822122017a2ce05533e864d9273119636089adc9ee39f4de7f45ce1cf3d069a136fe25d64736f6c634300080e0033";
    const setStatus = await provider.request({
      method: "evm_setAccountCode",
      params: [mockAddress, code],
    });
    assert.strictEqual(setStatus, true);
    const getCode = await provider.request({
      method: "eth_getCode",
      params: [mockAddress],
    });
    assert.strictEqual(code, getCode);
    let mockContract = await Hello.at(mockAddress);
  });
});
Error: Cannot create instance of Hello; no code at address 0xe2d3A739EFFCd3A99387d015E260eEFAc72EBea1
      at Object.checkCode (node_modules/truffle/build/webpack:/packages/contract/lib/utils/index.js:272:1)
      at Function.at (node_modules/truffle/build/webpack:/packages/contract/lib/contract/constructorMethods.js:73:1)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)
      at Context.<anonymous> (test/merchant/Hello.test.js:21:24)

It looks like the Contract.at() function, is not very easy to use. I think it would be more convenient if the new function could be extended. Contract.new([params],{targetAddress: "0xaddress"})

leiiiooo avatar Jun 06 '22 06:06 leiiiooo

Hi @leiiiooo, I don't think Contract.at is where the problem is. You can use web3.eth.getCode to verify that evm_setAccountCode succeeded before calling Contract.at. Could you try this and let me know if it works? Thanks!

await web3.currentProvider.send({
  method: "evm_setAccountCode",
  params: [addr, bytecode]
})

web3 is available in the test context with the correct provider.

cliffoo avatar Jun 07 '22 13:06 cliffoo

@cliffoo Hi, here is the code for your mentioned function. In the first step, I can read the code and print it, second step, I try to call evm_setAccountCode after that I try to call getCode again, seems nothing happened. Always 0x

it("byte code test", async function () {
  let helloInstance: HelloInstance = await Hello.new();
  let address: string = helloInstance.address;
  let addressCode = await web3.eth.getCode(address);
  console.log("addressCode: --- " + addressCode);
  let newAddress = "0xddfabcdc4d8ffc6d5beaf154f18b778f892a0740";
  await (web3.currentProvider as any).send({
    method: "evm_setAccountCode",
    params: [newAddress, addressCode],
  });
  let newAddressCode = await web3.eth.getCode(newAddress);
  console.log("newAddressCode: --- " + newAddressCode);
});
Contract: Hello test code
addressCode: --- 0x608060405234801561001057600080fd5b50600436106100415760003560e01c8063594f4d4a14610046578063b954e8a814610076578063fd5afcc114610092575b600080fd5b610060600480360381019061005b9190610134565b6100c2565b60405161006d919061017c565b60405180910390f35b610090600480360381019061008b9190610134565b6100c9565b005b6100ac60048036038101906100a79190610134565b6100cc565b6040516100b9919061017c565b60405180910390f35b6000919050565b50565b600060019050919050565b600080fd5b60007fffffffffffffffff00000000000000000000000000000000000000000000000082169050919050565b610111816100dc565b811461011c57600080fd5b50565b60008135905061012e81610108565b92915050565b60006020828403121561014a576101496100d7565b5b60006101588482850161011f565b91505092915050565b60008115159050919050565b61017681610161565b82525050565b6000602082019050610191600083018461016d565b9291505056fea2646970667358221220d77f282d34daca82dc75a8eeb088d3d83938a7e2bd8bdadc419ea177d026788f64736f6c634300080e0033
    1) byte code test
newAddressCode: --- 0x
    2) byte code test
    > No events were emitted

leiiiooo avatar Jun 08 '22 07:06 leiiiooo

What version of ganache are you using?

davidmurdoch avatar Jun 08 '22 12:06 davidmurdoch

Here are my deps. 🤔

"devDependencies": {
  "@openzeppelin/test-helpers": "0.5.15",
  "@typechain/truffle-v5": "8.0.0",
  "@types/bn.js": "5.1.0",
  "@types/chai": "4.3.1",
  "@types/mocha": "9.1.1",
  "@types/node": "^17.0.40",
  "@types/shelljs": "0.8.11",
  "chai": "4.3.6",
  "ganache": "7.0.5",
  "mocha": "10.0.0",
  "shelljs": "0.8.5",
  "solidity-coverage": "0.8.0-rc.1",
  "ts-node": "10.8.1",
  "typechain": "8.0.0",
  "typescript": "4.7.3"
},
"dependencies": {
  "@openzeppelin/contracts": "4.6.0",
  "solc": "0.8.14",
  "truffle": "5.5.15",
  "truffle-flattener": "^1.6.0"
}

leiiiooo avatar Jun 08 '22 12:06 leiiiooo

Can you update Ganache to the latest version, 7.2.0, and try again?

davidmurdoch avatar Jun 08 '22 13:06 davidmurdoch

Sad, the same.

> Everything is up to date, there is nothing to compile.


  Contract: Hello test code
addressCode: --- 0x608060405234801561001057600080fd5b50600436106100415760003560e01c8063594f4d4a14610046578063b954e8a814610076578063fd5afcc114610092575b600080fd5b610060600480360381019061005b9190610134565b6100c2565b60405161006d919061017c565b60405180910390f35b610090600480360381019061008b9190610134565b6100c9565b005b6100ac60048036038101906100a79190610134565b6100cc565b6040516100b9919061017c565b60405180910390f35b6000919050565b50565b600060019050919050565b600080fd5b60007fffffffffffffffff00000000000000000000000000000000000000000000000082169050919050565b610111816100dc565b811461011c57600080fd5b50565b60008135905061012e81610108565b92915050565b60006020828403121561014a576101496100d7565b5b60006101588482850161011f565b91505092915050565b60008115159050919050565b61017681610161565b82525050565b6000602082019050610191600083018461016d565b9291505056fea2646970667358221220d77f282d34daca82dc75a8eeb088d3d83938a7e2bd8bdadc419ea177d026788f64736f6c634300080e0033
    1) byte code test
newAddressCode: --- 0x
    2) byte code test
    > No events were emitted


  0 passing (521ms)
  2 failing

  1) Contract: Hello test code
       byte code test:
     Uncaught TypeError: callback is not a function
      at /Users/user/leiiiooo/project/android/company/R2-D2/node_modules/truffle/build/webpack:/packages/provider/wrapper.js:119:1
      at XMLHttpRequest.request.onreadystatechange (node_modules/truffle/build/webpack:/node_modules/web3/node_modules/web3-providers-http/lib/index.js:98:1)
      at XMLHttpRequestEventTarget.dispatchEvent (node_modules/truffle/build/webpack:/node_modules/xhr2-cookies/dist/xml-http-request-event-target.js:34:1)
      at XMLHttpRequest.exports.modules.996763.XMLHttpRequest._setReadyState (node_modules/truffle/build/webpack:/node_modules/xhr2-cookies/dist/xml-http-request.js:208:1)
      at XMLHttpRequest.exports.modules.996763.XMLHttpRequest._onHttpResponseEnd (node_modules/truffle/build/webpack:/node_modules/xhr2-cookies/dist/xml-http-request.js:318:1)
      at IncomingMessage.<anonymous> (node_modules/truffle/build/webpack:/node_modules/xhr2-cookies/dist/xml-http-request.js:289:48)
      at IncomingMessage.emit (node:events:402:35)
      at endReadableNT (node:internal/streams/readable:1343:12)
      at processTicksAndRejections (node:internal/process/task_queues:83:21)

  2) Contract: Hello test code
       byte code test:
     Error: done() called multiple times in test <Contract: Hello test code byte code test> of file /Users/user/leiiiooo/project/android/company/R2-D2/test/MerchantWallet.test.js
      at processTicksAndRejections (node:internal/process/task_queues:96:5)

leiiiooo avatar Jun 08 '22 13:06 leiiiooo

Hey @leiiiooo, so you need to provide a callback to web3.currentProvider.send as the second param.

Here's a working example that wraps it and returns a promise. Also as David mentioned, make sure you're running the latest version of Ganache. Give it a try!

cliffoo avatar Jun 08 '22 15:06 cliffoo

Yep, your codes are acceptable. 🤔 MAY be the root cause is the plugin solidity-coverage. It does not support the newest version of Ganache. In my code, when I call evm_setAccountCode, it will give me undefined. @cgewecke Do you have any good ideas about this? 🤔

leiiiooo avatar Jun 08 '22 15:06 leiiiooo

Closing for Issue Maintenance.

cliffoo avatar Jul 07 '22 17:07 cliffoo