sig-proxy 0.5.0

Signature-Based (Web3) Authenticating Proxy
use crate::WrappedResult;
use console::style;
use dialoguer::{theme::ColorfulTheme, Confirm, Select};
use structopt::StructOpt;

fn we_can_do_it() {
  println!("\nGood news! There's a preset for that:")
}

fn roll_your_own() {
  println!("\nOh no! You'll have to roll your own contract for that.")
}

fn suggest_custom_mintable_with_operators() {
  roll_your_own();
  println!(
    "
  You can still leverage OpenZeppelin though!

  If you extend the {erc777} contract, add the {access_control} library, and 
  implement minting, you should be most of the way to what you need!

  Read more:

    {erc777} Specification: {spec}
    {erc777} Token API: {erc777_api}
    {access_control} API: {access_control_api}
    ",
    access_control = style("AccessControl").bold().white().on_black(),
    access_control_api =
      style("https://docs.openzeppelin.com/contracts/3.x/api/access#AccessControl").underlined(),
    erc777 = style("ERC777").bold().blue(),
    erc777_api = style("https://docs.openzeppelin.com/contracts/3.x/api/token/erc777").underlined(),
    spec = style("https://eips.ethereum.org/EIPS/eip-777").underlined(),
  );
}

fn read_more(preset: &str, erc: &str, api: &str, spec: &str) {
  println!(
    "
  Read more:

    {erc} Tokens: {spec}
    {preset} API: {api}
    ",
    api = style(api).underlined(),
    erc = style(erc).bold().blue(),
    preset = style(preset).bold().white().on_black(),
    spec = style(spec).underlined(),
  )
}

fn deploy_with(command: &str) {
  println!(
    "
  Deploy with:

    {command}

  (check --help for options)
    ",
    command = style(command).bold().white().on_black(),
  )
}

fn openzeppelin_erc20_preset_fixed_supply() {
  we_can_do_it();
  println!(
    "
  {preset} (from OpenZeppelin)

  This is an {erc} token, including:
    - Preminted initial supply
    - Ability for {holders} to {burn} (destroy) their tokens
    - No access control mechanism (for {minting}/{pausing}) and hence no governance
    ",
    burn = style("burn").bright().red().on_black(),
    erc = style("ERC20").bold().blue(),
    holders = style("holders").bold().yellow().on_black(),
    minting = style("minting").bright().green().on_black(),
    pausing = style("pausing").bright().cyan().on_black(),
    preset = style("ERC20PresetFixedSupply").bold().white().on_black(),
  );

  read_more(
    "ERC20PresetFixedSupply",
    "ERC20",
    "https://docs.openzeppelin.com/contracts/3.x/api/presets#ERC20PresetFixedSupply",
    "https://eips.ethereum.org/EIPS/eip-20",
  );

  deploy_with("sig-proxy contract deploy ERC20PresetFixedSupply");
}

fn openzeppelin_preset_erc20_preset_minter_pauser() {
  we_can_do_it();
  println!(
    "
  {preset} (from OpenZeppelin)

  This is an {erc} token, including:
    - ability for {holders} to {burn} (destroy) their tokens
    - a {minter} role that allows for token {minting} (creation)
    - a {pauser} role that allows to {stop} all token transfers

  The account that deploys the contract will be granted the {minter} 
  and {pauser} roles, as well as the default {admin} role, which will 
  let it grant both {minter} and {pauser} roles to other accounts.
    ",
    admin = style("admin").bold().magenta().on_black(),
    burn = style("burn").bright().red().on_black(),
    erc = style("ERC20").bold().blue(),
    holders = style("holders").bold().yellow().on_black(),
    minter = style("minter").bold().green().on_black(),
    minting = style("minting").bright().green().on_black(),
    pauser = style("pauser").bold().cyan().on_black(),
    preset = style("ERC20PresetMinterPauser").bold().white().on_black(),
    stop = style("stop").cyan().on_black(),
  );

  read_more(
    "ERC20PresetMinterPauser",
    "ERC20",
    "https://docs.openzeppelin.com/contracts/3.x/api/presets#ERC20PresetMinterPauser",
    "https://eips.ethereum.org/EIPS/eip-20",
  );

  deploy_with("sig-proxy contract deploy ERC20PresetMinterPauser");
}

fn openzeppelin_preset_erc721_preset_minter_pauser_auto_id() {
  we_can_do_it();
  println!(
    "
  {preset} (from OpenZeppelin)

  This is an {erc} token, including:
    - ability for {holders} to {burn} (destroy) their tokens
    - a {minter} role that allows for token {minting} (creation)
    - a {pauser} role that allows to {stop} all token transfers
    - token ID and URI autogeneration

  The account that deploys the contract will be granted the {minter} 
  and {pauser} roles, as well as the default {admin} role, which will 
  let it grant both {minter} and {pauser} roles to other accounts.
    ",
    admin = style("admin").bold().magenta().on_black(),
    burn = style("burn").bright().red().on_black(),
    erc = style("ERC721").bold().blue(),
    holders = style("holders").bold().yellow().on_black(),
    minter = style("minter").bold().green().on_black(),
    minting = style("minting").bright().green().on_black(),
    pauser = style("pauser").bold().cyan().on_black(),
    preset = style("ERC721PresetMinterPauserAutoId")
      .bold()
      .white()
      .on_black(),
    stop = style("stop").cyan().on_black(),
  );

  read_more(
    "ERC721PresetMinterPauserAutoId",
    "ERC721",
    "https://docs.openzeppelin.com/contracts/3.x/api/presets#ERC721PresetMinterPauserAutoId",
    "https://eips.ethereum.org/EIPS/eip-721",
  );

  deploy_with("sig-proxy contract deploy ERC721PresetMinterPauserAutoId");
}

fn openzeppelin_preset_erc777_preset_fixed_supply() {
  we_can_do_it();
  println!(
    "
  {preset} (from OpenZeppelin)

  This is an {erc} token, including:
    - Preminted initial supply
    - No access control mechanism (for {minting}/{pausing}) and hence no governance
    ",
    preset = style("ERC777PresetFixedSupply").bold().white().on_black(),
    erc = style("ERC777").bold().blue(),
    minting = style("minting").bright().green().on_black(),
    pausing = style("pausing").bright().cyan().on_black(),
  );

  read_more(
    "ERC777PresetFixedSupply",
    "ERC777",
    "https://docs.openzeppelin.com/contracts/3.x/api/presets#ERC777PresetFixedSupply",
    "https://eips.ethereum.org/EIPS/eip-777",
  );

  deploy_with("sig-proxy contract deploy ERC777PresetFixedSupply");
}

fn openzeppelin_preset_erc1155_preset_fixed_supply() {
  we_can_do_it();
  println!(
    "
  {preset} (from OpenZeppelin)

  This is an {erc} token, including:
  - ability for {holders} to {burn} (destroy) their tokens
  - a {minter} role that allows for token {minting} (creation)
  - a {pauser} role that allows to {stop} all token transfers

  The account that deploys the contract will be granted the {minter} 
  and {pauser} roles, as well as the default {admin} role, which will 
  let it grant both {minter} and {pauser} roles to other accounts.
    ",
    preset = style("ERC1155PresetFixedSupply").bold().white().on_black(),
    erc = style("ERC1155").bold().blue(),
    burn = style("burn").bright().red().on_black(),
    minting = style("minting").bright().green().on_black(),
    holders = style("holders").bold().yellow().on_black(),
    minter = style("minter").bold().green().on_black(),
    pauser = style("pauser").bold().white().on_black(),
    admin = style("admin").bold().magenta().on_black(),
    stop = style("stop").red().on_black(),
  );

  read_more(
    "ERC1155PresetFixedSupply",
    "ERC1155",
    "https://docs.openzeppelin.com/contracts/3.x/api/presets#ERC1155PresetMinterPauser",
    "https://eips.ethereum.org/EIPS/eip-1155",
  );

  deploy_with("sig-proxy contract deploy ERC1155PresetFixedSupply")
}

fn choose_a_contract() -> WrappedResult<()> {
  let theme = ColorfulTheme::default();
  let fungible = Select::with_theme(&theme)
    .with_prompt("Are tokens fungible?")
    .item("Yes, any token is equivalent to any other token (like points).")
    .item("No, each token is unique (like subscriptions).")
    .default(0)
    .interact()?
    .eq(&0);

  let mintable = if fungible {
    Confirm::with_theme(&theme)
      .with_prompt("Can the token supply be increased after deployment?")
      .interact()?
  } else {
    false
  };

  let operators = if fungible {
    Confirm::with_theme(&theme)
      .with_prompt(
        "Do you require operators (special accounts that can transfer on behalf of token holders)?",
      )
      .interact()?
  } else {
    false
  };

  let bundles = if !fungible {
    Confirm::with_theme(&theme)
      .with_prompt("Do you need to bundle multiple tokens types under a single contract?")
      .interact()?
  } else {
    false
  };

  match fungible {
    false => match bundles {
      false => openzeppelin_preset_erc721_preset_minter_pauser_auto_id(),
      true => openzeppelin_preset_erc1155_preset_fixed_supply(),
    },
    true => match (mintable, operators) {
      (false, false) => openzeppelin_erc20_preset_fixed_supply(),
      (false, true) => openzeppelin_preset_erc777_preset_fixed_supply(),
      (true, false) => openzeppelin_preset_erc20_preset_minter_pauser(),
      (true, true) => suggest_custom_mintable_with_operators(),
    },
  }

  Ok(())
}

#[derive(Debug, StructOpt)]
#[structopt(about = "Interactive Guides")]
pub enum Command {
  #[structopt(about = "Helps you decide which contract to use.")]
  ChooseAContract,
}

impl Command {
  pub(crate) fn execute(self) -> WrappedResult<()> {
    match self {
      Self::ChooseAContract => choose_a_contract(),
    }
  }
}