Possible to add newly deployed contracts to Defender via (deploy-)script=?

Hey Guys,

is it possible to add contracts to Defender via script/API?
It seems like a lot of manual work to add every contract, upload its ABI etc...
It would be great if we could include that in our deploy script. Is that possible?

Cheers,
Daniel

You can use the Admin Contracts API endpoint: https://docs.openzeppelin.com/defender/admin-api-reference#contracts-endpoint

1 Like

Awesome Martin, thank you so much. I thought I had seen something like that somewhere but couldnt find it. Appreciate your support.

Hey @MartinVerzilli,

sorry one more question. Is there an easy way to get the ABI from a contract that I have just created/deployed? I just cant find anything on Google but I am sure there must be some kind of function like contract.getABI() or so.

What I want to do is to deploy lets say 10 contracts and then automatically add them to Sentinel. I managed to write a generic task with parameters (contractAddress, contractName, network, abi) that I can call after deployment of each contract with the parameter of that specific contract....now I just need to get the ABI of the contract at runtime.

Do you happen to have any idea how to do that?

Not sure if you refer to contracts in general or the contracts you imported to Defender in particular. If you refer to your contracts imported to Defender, you can just GET them from the same endpoint I linked in my previous message.

If you mean contracts in general, you need the original source code of a contract to get its ABI. My (personal) preferred workflow is to use Hardhat to deploy the contract and the Hardhat Etherscan plugin (https://hardhat.org/plugins/nomiclabs-hardhat-etherscan.html) to verify the code as soon as I deployed. That way I don't need to keep track of which source code originated each of my contracts. It's stored forever in Etherscan.

When you enter the address of a contract whose source code was verified, Defender pulls it automatically for you. Internally, Defender uses Etherscan's API to discover where the source code was verified, and thus the ABI is available. You can do the same with your automated scripts when you're working with contracts that still haven't been imported to Admin: https://etherscan.io/apidocs#contracts. Note though that you need to verify your contract's source code for this to work.

Hi Martin,

yes this is exactly what I was looking for. My goal is to have a deploy script that deploys contracts and adds them to our Defender with all information including ABI to simplify our workflow.

Now first of all - thank you so much for your help so far. I wouldn't be where I am already (just before the finish line) without your help.

I have set up a task now that I want to include in my deploy script. But I am getting a weird error message and I cant find any solution on Google. Maybe you have an idea?

The task looks like this:

task('tasks/verifyContractOnEtherscan', 'Verifies the code of a deployed contract on Etherscan')
  .addParam('contractAddress', 'Address of the contract to be added', types.address)
  .addParam('constructorArguments', 'The arguments that were passed to the constructor during contract deployment')
  .setAction(async ({ contractAddress, constructorArguments } ) => {

    await hre.run("verify:verify", {
      address: contractAddress,
      constructorArguments
    });
    

    console.log("Contract with address %s successfully verified on Etherscan ✅", contractAddress );
});

and I call it like this:

    // Deploy Bridge token
    const bridgeTokenAddress = await run('lib/token/deployBridgeToken');

    console.log(1);
    
    // verify contract on Etherscan
    await run('tasks/verifyContractOnEtherscan', { contractAddress: bridgeTokenAddress, constructorArguments: [0] });
    console.log(2);

and this is the error message I am getting:

I dont understand why type string is asked here. By whom? Unfortunately I cannot check the code of the verify:verify task. And I didnt add a string type to my task params.
Even if I put the 0 in "" (effectively making it a string) the error message remains same.
Do you happen to know what am I missing?

Sorry for taking so much of your time.

I just realized something:

  • the deployed address is not "my" contract but the proxy contract, which has already been verified and may have a string in the constructor, so this is where this error probably comes from

Which actually puts me two steps back, cause this makes it more difficult for me to programmatically verify the contract.

Do you have a working example for:

  • deploying an upgradeable contract
  • getting the address of the implementation contract (not the proxy)
  • using this address to verify the contract on Etherscan
  • add the verfied contract to Defender (which, according to your statement, will automaticall pull its ABI from Etherscan)
    ???

I think this would be a really valuable resource as a how-to-guide since a bunch of people seem to be searching for this solution online. I will be happy to contribute to the guide.

I do have something that comes really close to what you need: a workshop on managing upgrades combining OZ Contracts, the Upgrades Plugins, and Defender: https://www.youtube.com/watch?v=2T3vD5gFJAc&t=2s.

You can find the code with detailed instructions here: https://github.com/OpenZeppelin/workshops/tree/master/05-upgrades-management

There you'll find how to get the implementation address when you deploy an upgradeable contract using our tools.

Note that this is only true when you add a contract from Defender's UI. If you do it from the Admin API you'll need to provide the ABI yourself (for example by fetching it from Etherscan's ABI API).

1 Like

Hi Martin,

thanks for the information. I went through the video and your code.
However, I was not able to find the part where you get the implementation address after deployment.
Your script is this:

image

But box.address will show the address of the proxy contract, not the implementation. When I enter this address on etherscan I will see the verified code of the proxy. It does not have the button "read/write as proxy" as in your examples, which I dont understand either.

Please can you point me into the direction how to find the implementation contract address after deployment via code (not by manually going to Etherscan)?

Ah, I see. I think as we speak the Upgrades Plugins team is working on a PR to expose a getImplementationAddress, but in the meantime you'd have to read the implementation address from your proxy's storage. The slots could be in one of two places:

const IMPLEMENTATION_LABEL = 'eip1967.proxy.implementation';
const DEPRECATED_IMPLEMENTATION_LABEL = 'org.zeppelinos.proxy.implementation';

// (BigNumber, hexlify and id are available in Ethers JS)
const IMPLEMENTATION_LABEL_HASH = hexlify(BigNumber.from(id(IMPLEMENTATION_LABEL)).sub(1));

const DEPRECATED_IMPLEMENTATION_LABEL_ID = id(DEPRECATED_IMPLEMENTATION_LABEL);

So you'd have to call getStoragetAt(box.address, IMPLEMENTATION_LABEL_HASH) and/or getStorageAt(box.address, DEPRECATED_IMPLEMENTATION_LABEL_ID)

1 Like

Oh I feel I am so close but still not there unfortunately.....thanks for staying with me, Martin :slightly_smiling_face:

Here's my task with various options I've tried. Cant get it to work. Contract is deployed to rinkeby network.
What am I missing?

require('@nomiclabs/hardhat-web3');
// const { getImplementationAddress } = require('@openzeppelin/upgrades-core');
const { hexlify, BigNumber, ethers } = require('ethers');

const IMPLEMENTATION_LABEL = 'eip1967.proxy.implementation';
const DEPRECATED_IMPLEMENTATION_LABEL = 'org.zeppelinos.proxy.implementation';

task('danielTest', 'Tests Daniels amazing new functionalities')
.setAction(async () => {

    const IMPLEMENTATION_LABEL_HASH = ethers.utils.hexlify(BigNumber.from(ethers.utils.id(IMPLEMENTATION_LABEL)).sub(1));
    const DEPRECATED_IMPLEMENTATION_LABEL_ID = ethers.utils.id(DEPRECATED_IMPLEMENTATION_LABEL);

    // deploy token
    const Contract = await hre.ethers.getContractFactory('Bridge');
    const contract = await hre.upgrades.deployProxy(Contract, [0]);

    console.log("contract address: %s", contract.address);      // address of the proxy -  works fine

    let provider = ethers.provider; //  does not work: "TypeError: Cannot read property 'getStoragetAt' of undefined"
    //let provider = ethers.getDefaultProvider(); // does not work: "TypeError: provider.getStoragetAt is not a function"
    //let provider = ethers.provider(); //  does not work: "TypeError: ethers.provider is not a function"
    //let provider = new ethers.providers.InfuraProvider('rinkeby'); // does not work: "TypeError: provider.getStoragetAt is not a function"
    //let provider = ethers.getDefaultProvider("rinkeby"); // does not work: "TypeError: provider.getStoragetAt is not a function"

    let result = await provider.getStoragetAt(contract.address, IMPLEMENTATION_LABEL_HASH); // does not work: "TypeError: Cannot read property 'getStoragetAt' of undefined"
    // let result = await ethers.provider.getStoragetAt(contract.address, IMPLEMENTATION_LABEL_HASH); // does not work: "TypeError: Cannot read property 'getStoragetAt' of undefined"
    console.log("result: %s", result);
});

Feel like I owe you 10 beers already. Thanks a lot again for this great support.

I think you need to require ethers from hardhat, not from ethers:

const { ethers } = require("hardhat");
const { hexlify, BigNumber } = require('ethers');

That also doesnt work.

I've noticed that your colleague already submitted the PR for this function so I guess it makes sense to just wait and not waste any more of your time. Nevertheless, thanks a lot for your persistence !