Deployments

Configuration

Every contract deployment requires an explicit configuration. Specifically, the configuration is an object of the following type:

type DeployReceipt args =
  { deployAddress :: Address
  , deployArgs :: Record args
  , deployHash :: HexString
  }

type ContractConfig args =
  { filepath :: String
  , name :: String
  , constructor :: Constructor args
  , unvalidatedArgs :: V (Array String) (Record args)
  }

The filepath field is the filepath to the solc build artifact relative the the chanterelle.json file.

The name field is there to name the deployment throughout the logging. (This could dissappear assuming its suffient to name the deployment according to the build artifact filename.)

The type Constructor args is a type synonym:

type Constructor args = TransactionOptions NoPay -> HexString -> Record args -> Web3 HexString

In other words, Constructor args is the type a function taking in some TransactionOptions NoPay (constructors are not payable transactions), the deployment bytecode as a PureScript HexString, and a record of type Record args. It will format the transaction and submit it via an eth_sendTransaction RPC call, returning the transaction hash as a HexString. This constructor function is generated by Chanterelle as part of the codegen step. Note that this function is taking in the raw bytecode as an argument. This is because your contract may have unlinked libraries and it doesn’t make sense to generate it into the PureScript binding. When you call deployContract with your ContractConfig, Chanterelle will automatically feed in the appropriate bytecode.

The unvalidatedArgs field has type V (Array String) (Record args) where V is a type coming from the purescript-validation library. This effectively represents either a type of Record args or a list of error messages for all arguments which failed to validate.

It’s possible that your contract requires no arguments for deployment, and in that case chanterelle offers some default values. For example, if the filepath of the build artifact for VerySimpleContract.sol is build/VerySimpleContract.json, you might end up with something like

verySimpleContractConfig :: ContractConfig NoArgs
verySimpleContractConfig =
  { filepath: "build/VerySimpleContract.json"
  , name: "VerySimpleContract"
  , constructor: constructorNoArgs
  , unvalidatedArgs: noArgs
  }

Let’s consider the simplest example of a contract configuration requiring a constructor with arguments. Consider the following smart contract:

contract SimpleStorage {

  uint256 count public;

  event CountSet(uint256 _count);

  function SimpleStorage(uint256 initialCount) {
    count = initialCount;
  }

  function setCount(uint256 newCount)) {
    count = newCount;
    emit CountSet(newCount));
  }

}

Depending on your project configuration, when running chanterelle compile you should end up with something like the following artifacts:

  1. The solc artifact build/SimpleStorage.json
  2. The generated PureScript file src/Contracts/SimpleStorage.purs

In the PureScript module Contracts.SimpleStorage, you will find a function

constructor :: TransactionOptions NoPay -> HexString -> {initialCount :: UIntN (D2 :& D5 :& DOne D6)} -> Web3 HexString

Blurring your eyes a little bit, it’s easy to see that this indeed matches up to the constructor defined in the Solidity file. We could then define the deployment configuration for SimpleStorage as

import Contracts.SimpleStorage as SimpleStorage

simpleStorageConfig :: ContractConfig (initialCount :: UIntN (D2 :& D5 :& DOne D6))
simpleStorageConfig =
    { filepath: "build/SimpleStorage.json"
    , name: "SimpleStorage"
    , constructor: SimpleStorage.constructor
    , unvalidatedArgs: validCount
    }
  where
    validCount = uIntNFromBigNumber s256 (embed 1234) ?? "SimpleStorage: initialCount must be valid uint256"

Here you can see where validation is important. Clearly 1234 represents a valid uint, but you can easily imagine scenarios where this might save us a lot of trouble– too many characters in an address, an improperly formatted string, an integer is out of a bounds, etc.

Deploy Scripts

Deploy scripts are written inside the DeployM monad, which is a monad that gives you access to a web3 connection, controlled error handling, and whatever effects you want. The primary workhorse is the deployContract function:

deployContract :: TransactionOptions NoPay -> ContractConfig args -> DeployM (DeployReceipt args)

This function takes your contract deployment configuration as defined above and sends the transaction. If no errors are thrown, it will return the address where the contract as deployed as well as the deploy arguments that were validated before the transaction was sent. It will also automatically write to the solc artifact in the artifacts-dir, updating the networks object with a key value pair mapping the networkId to the deployed address.

Error hanlding is built in to the DeployM monad. Unless you want to customize your deployment with any attempt to use some variant of try/catch, any error encountered before or after a contract deployment will safely terminate the script and you should get an informative message in the logs. It will not terminate while waiting for transactions to go through unless the timeout threshold is reached. You can configure the duration as a command line argument.

Deployment Example

Consider this example take from the parking-dao example project:

module MyDeployScript where

import ContractConfig (simpleStorageConfig, foamCSRConfig, parkingAuthorityConfig)

deploy :: DeployM Unit
deploy = void deployScript

type DeployResults = (foamCSR :: Address, simpleStorage :: Address, parkingAuthority :: Address)
deployScript :: DeployM (Record DeployResults)
deployScript = do
  deployCfg@(DeployConfig {primaryAccount}) <- ask
  let bigGasLimit = unsafePartial fromJust $ parseBigNumber decimal "4712388"
      txOpts = defaultTransactionOptions # _from ?~ primaryAccount
                                         # _gas ?~ bigGasLimit
  simpleStorage <- deployContract txOpts simpleStorageConfig
  foamCSR <- deployContract txOpts foamCSRConfig
  let parkingAuthorityConfig = makeParkingAuthorityConfig {foamCSR: foamCSR.deployAddress}
  parkingAuthority <- deployContract txOpts parkingAuthorityConfig
  pure { foamCSR: foamCSR.deployAddress
       , simpleStorage: simpleStorage.deployAddress
       , parkingAuthority: parkingAuthority.deployAddress
       }

After setting up the TransactionOptions, the script first deploys the SimpleStorage contract and then the FoamCSR contract using their configuration. The ParkingAuthority contract requires the address of the FoamCSR contract as one of it’s deployment arguments, so you can see us threading it in before deploying. Finally, we simple return all the addresses of the recently deployed contracts to the caller.

Note that if we simply wanted to terminate the deployment script after the contract deployments there then there’s no point in returning anything at all. However, deployment scripts are useful outside of the context of a standalone script. For example you can run a deployment script before a test suite and then pass the deployment results as an environment to the tests. See the section on testing for an example.

Libraries

Library deployments are very similar to those of contracts. Libraries exist as code that simply exists on the blockchain and gets invoked by other contracts. As such, library contracts do not have constructors nor do they take any arguments, and all that is necessary to specify them is the file path to the artifact and the name of the Library contract.

type LibraryConfig args =
    { filepath :: String
    , name :: String
    | args
    }

You may find that this looks strangely similar to the type definition for ContractConfig, and are also probably curious as to what purpose the args serves. Well, to keep Chanterelle internals and general as possible, the ContractConfig is actually a subset of LibraryConfig, and the args in LibraryConfig exists to add additional fields to the record type internally. In fact, the previous definition of ContractConfig was a bit of a lie, it’s really type ContractConfig args = LibraryConfig (constructor :: Constructor args, unvalidatedArgs :: V (Array String) (Record args)). What this is saying is that the bare minimum for deploying any kind of solidity code with Chanterelle is the path to the artifact and the name of the code. Either way, once you’ve defined a configuration for your library, you may deploy it using:

type LibraryMeta = (libraryName :: String, libraryAddress :: Address)

deployLibrary :: TransactionOptions NoPay -> LibraryConfig () -> DeployM (DeployReceipt LibraryMeta)

This looks eerily similar to deployContract, except instead of a ContractConfig args, it takes a LibraryConfig (). The type prevents you from trying to deploy a contract as a Library (with no constructor or arguments), and similar to the deployContract function which returned the validated arguments passed into the constructor, deployLibrary returns some metadata about the Library’s name and the address it was deployed to. This is meant to be used in conjunction with the linkLibrary function:

linkLibrary:: ContractConfig args -> Record LibraryMeta -> DeployM ArtifactBytecode

What this is saying is “given an artifact configuration, and a record containing library metadata, link the library into the contract bytecode. Note that Record LibraryMeta is exactly the same type as that of the deployArgs :: Record args in DeployReceipt args. This means that if you had a contract named MyContract that needs a libraries named MyLibrary and MyOtherLibrary linked into it to function, you can do something akin to:

deployScript = do
  deployCfg@(DeployConfig {primaryAccount}) <- ask
  let bigGasLimit = unsafePartial fromJust $ parseBigNumber decimal "4712388"
      txOpts = defaultTransactionOptions # _from ?~ primaryAccount
                                         # _gas ?~ bigGasLimit
  myLibrary <- deployLibrary txOpts myLibraryConfig
  myOtherLibrary <- deployLibrary txOpts myOtherLibraryConfig
  _ <- linkLibrary myContractConfig myLibrary.deployArgs
  _ <- linkLibrary myContractConfig myOtherLibrary.deployArgs
  myContract <- deployContract txOpts myContractConfig

The linkLibrary function returns the linked bytecode for situations in which you wish to inspect how your bytecode changes as libraries gets linked in. You do not have to hold on to it or otherwise use it for anything.

Invocation

depending on your setup you should make sure MyDeployScript module is built. in most cases you can access corresponding js file in ./output/MyDeployScript/index.js which should be passed to chanterelle deploy command like this:

chanterelle deploy ./output/MyDeployScript/index.js