Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to write unit tests against the proxy contract using taqueria? #4

Open
mastercodercat opened this issue Aug 31, 2023 · 5 comments

Comments

@mastercodercat
Copy link

I wrote NFT collection smart contract using this tutorial, and also wrote proxy contract for upgradability. However, I encountered into an error while writing unit test cases against the proxy contract.
Anyone can help me for this issue?

@zamrokk
Copy link
Collaborator

zamrokk commented Aug 31, 2023

Hey

Do you have the unit tests to reproduce it?

@mastercodercat
Copy link
Author

Yes, sure. I can share it.
Here's the code.

#include "collection.storageList.jsligo"
#import "./collection.proxy.jsligo" "CollectionProxy"
#import "./collection.proxy.storageList.jsligo" "CPS"

const create_proxy = (): address => {
  const init_storage = Test.compile_value(CPS.default_storage);
  const [proxy_addr, _, _] = Test.originate_from_file(
    "./collection.proxy.jsligo",
    "main",
    list([]),
    init_storage,
    0 as tez
  );
  Test.log(["Collection Proxy address", proxy_addr]);

  return proxy_addr;
}

const create_collection = (proxy_addr: address): address => {
  const [caddr, _, _] = Test.originate_from_file(
    "./collection.jsligo",
    "main",
    list([]),
    Test.compile_value({
      ...default_storage,
      tzip18: {
        ...default_storage.tzip18,
        proxy: proxy_addr,
      }
    }),
    0 as tez
  );
  Test.log(["Collection address", caddr]);

  return caddr;
}

const test_call = (): typed_address<CollectionProxy.parameter, CollectionProxy.storage> => {
  const proxy_addr = create_proxy();
  const caddr = create_collection(proxy_addr);

  const typed_addr = Test.cast_address(proxy_addr) as typed_address<CollectionProxy.parameter, CollectionProxy.storage>;

  const contr = Test.to_contract(typed_addr);
  let gas_cons = Test.transfer_to_contract_exn(
    contr,
    Upgrade([
      list([{
        name: "Initialize",
        isRemoved: false,
        entrypoint: Some({
          method: "Initialize",
          addr: caddr,
        })
      }]),
      None() as option<CollectionProxy.changeVersion>
    ]),
    0 as mutez
  );
  Test.log(["Gas cons", gas_cons]);

  gas_cons = Test.transfer_to_contract_exn(
    contr,
    Call({
      entrypointName: "Initialize",
      payload: Bytes.pack([
        bytes `"Pastel"`,
        bytes `"PSL"`,
        { num: 1 as nat, den: 3 as nat } as rational,
        ("tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" as address)
      ])
    }),
    0 as mutez
  );
  Test.log(["Gas cons", gas_cons]);

  return typed_addr;
}

const t_call = test_call();

@mastercodercat
Copy link
Author

Here's the proxy smart contract code.

type callContract = {
  entrypointName: string,
  payload: bytes
};

type entrypointType = {
  method: string,
  addr: address
};

type entrypointOperation = {
  name: string,
  isRemoved: bool,
  entrypoint: option<entrypointType>
};

type changeVersion = {
  oldAddr: address,
  newAddr: address
};

type storage = {
  initialized: bool,
  governance: address, //admins
  entrypoints: big_map<string, entrypointType> //interface schema map
};

type parameter =
  | ["Initialize"]
  | ["UpdateGovernance", address]
  | ["Call", callContract]
  | ["Upgrade", list<entrypointOperation>, option<changeVersion>];

type _return = [list<operation>, storage];

const initialize = (s: storage): _return => {
  if (s.initialized) {
    return failwith("Already initialized");
  }

  return [list([]) as list<operation>, {
    ...s,
    governance: Tezos.get_sender(),
  }];
}

const updateGovernance = (addr: address, s: storage): _return => {
  if (Tezos.get_sender() != s.governance) {
    return failwith("Permission denied");
  }

  return [list([]) as list<operation>, {
    ...s,
    governance: addr,
  }];
}

// the proxy function
const callContract = ([param, storage]: [callContract, storage]): _return => {
  return match(Big_map.find_opt(param.entrypointName, storage.entrypoints), {
    None: () => failwith("No entrypoint found"),
    Some: (entry: entrypointType) =>
      match(
        Tezos.get_contract_opt(entry.addr) as option<contract<callContract>>,
        {
          None: () => failwith("No contract found at this address.."),
          Some: (contract: contract<callContract>) => [
            list([
              Tezos.transaction(
                { entrypointName: entry.method, payload: param.payload },
                Tezos.get_amount(),
                contract
              )
            ]) as list<operation>,
            storage
          ]
        }
      ),
  });
};

/**
 * Function for administrators to update entrypoints and change current contract version
 **/
const upgrade = ([param, s]: [
  [list<entrypointOperation>, option<changeVersion>],
  storage
]): _return => {
  if (Tezos.get_sender() != s.governance) {
    return failwith("Permission denied.");
  }

  let [upgraded_ep_list, changeVersionOpt] = param;

  const update_storage = ([l, m]: [
    list<entrypointOperation>,
    big_map<string, entrypointType>
  ]): big_map<string, entrypointType> => {
    return match(
      l,
      list([
        ([]: list<entrypointOperation>) => m,
        ([x, ...xs]: list<entrypointOperation>) => {
          let b: big_map<string, entrypointType> = match(x.entrypoint, {
            None: () => {
              if (x.isRemoved == true) {
                return Big_map.remove(x.name, m);
              } else {
                return m;
              }
            }, //mean to remove or unchanged
            Some: (_ep: entrypointType) => {
              //means to add new or unchanged
              if (x.isRemoved == false) {
                return match(x.entrypoint, {
                  None: () => m,
                  Some: (c: entrypointType) =>
                    Big_map.update(x.name, Some(c), m),
                });
              } else {
                return m;
              }
            },
          });
          return update_storage([xs, b]);
        }
      ])
    );
  };

  //update the entrpoint interface map
  const new_entrypoints: big_map<string, entrypointType> = update_storage([
    upgraded_ep_list,
    s.entrypoints
  ]);

  //check if version needs to be changed
  return match(changeVersionOpt, {
    None: () => [
      list([]) as list<operation>,
      { ...s, entrypoints: new_entrypoints }
    ],
    Some: (change: changeVersion) => {
      let op_change: operation = match(
        Tezos.get_contract_opt(change.oldAddr) as option<
          contract<callContract>
        >,
        {
          None: () => failwith("No contract found at this address"),
          Some: (contract: contract<callContract>) => {
            let amt = Tezos.get_amount();
            let payload: address = change.newAddr;
            return Tezos.transaction(
              { entrypointName: "changeVersion", payload: Bytes.pack(payload) },
              amt,
              contract
            );
          },
        }
      );
      return [
        list([op_change]) as list<operation>,
        { ...s, entrypoints: new_entrypoints }
      ];
    },
  });
};

export const main = ([p, s]: [parameter, storage]): _return => {
  return match(p, {
    Initialize: () => initialize(s),
    UpdateGovernance: (p: address) => updateGovernance(p, s),
    Call: (p: callContract) => callContract([p, s]),
    Upgrade: (p: [list<entrypointOperation>, option<changeVersion>]) => upgrade([p, s]),
  });
};

// @view
const getView = ([viewName, store]: [string, storage]): bytes => {
  return match(Big_map.find_opt(viewName, store.entrypoints), {
    None: () => failwith("View " + viewName + " not declared on this proxy"),
    Some: (ep: entrypointType) =>
      Option.unopt(
        Tezos.call_view("getView", viewName, ep.addr) as option<bytes>
      ),
  });
};

@zamrokk
Copy link
Collaborator

zamrokk commented Sep 4, 2023

Hi @mastercodercat , I sent you an invitation for a call today. I don't see that you are available.
Please reschedule it this week if necessary

@mastercodercat
Copy link
Author

mastercodercat commented Sep 4, 2023 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants