Skip to content

Commit

Permalink
feat: move title escrow into deployer (Open-Attestation#101)
Browse files Browse the repository at this point in the history
  • Loading branch information
superical committed Apr 28, 2022
1 parent da6a787 commit 174d52d
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 98 deletions.
6 changes: 2 additions & 4 deletions contracts/TradeTrustERC721Impl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ contract TradeTrustERC721Impl is TradeTrustERC721Base {
constructor() initializer {}

function initialize(bytes memory params) external initializer {
(string memory name, string memory symbol, address titleEscrowFactory_, address admin) = abi.decode(
params,
(string, string, address, address)
);
(bytes memory _params, address titleEscrowFactory_) = abi.decode(params, (bytes, address));
(string memory name, string memory symbol, address admin) = abi.decode(_params, (string, string, address));
_genesis = block.number;
_titleEscrowFactory = titleEscrowFactory_;
__TradeTrustERC721Base_init(name, symbol, admin);
Expand Down
41 changes: 0 additions & 41 deletions contracts/utils/ImplDeployer.sol

This file was deleted.

50 changes: 50 additions & 0 deletions contracts/utils/TDocDeployer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/proxy/ClonesUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract TDocDeployer is OwnableUpgradeable, UUPSUpgradeable {
event Deployment(
address indexed deployed,
address indexed implementation,
address indexed deployer,
address titleEscrowFactory,
bytes params
);

// mapping: implementation => title escrow factory
mapping(address => address) public implementations;

constructor() initializer {}

function initialize() external initializer {
__Ownable_init();
}

function _authorizeUpgrade(address) internal view override onlyOwner {}

function deploy(address implementation, bytes memory params) external returns (address) {
address titleEscrowFactory = implementations[implementation];
require(titleEscrowFactory != address(0), "TDocDeployer: Not whitelisted");

address deployed = ClonesUpgradeable.clone(implementation);
bytes memory payload = abi.encodeWithSignature("initialize(bytes)", abi.encode(params, titleEscrowFactory));
(bool success, ) = address(deployed).call(payload);
require(success, "TDocDeployer: Init fail");

emit Deployment(deployed, implementation, msg.sender, titleEscrowFactory, params);
return deployed;
}

function addImplementation(address implementation, address titleEscrowFactory) external onlyOwner {
require(implementations[implementation] == address(0), "TDocDeployer: Already added");
implementations[implementation] = titleEscrowFactory;
}

function removeImplementation(address implementation) external onlyOwner {
require(implementations[implementation] != address(0), "TDocDeployer: Invalid implementation");
delete implementations[implementation];
}
}
8 changes: 2 additions & 6 deletions src/utils/encode-init-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@ import { ethers } from "ethers";
interface Params {
name: string;
symbol: string;
titleEscrowFactory: string;
deployer: string;
}

export const encodeInitParams = ({ name, symbol, titleEscrowFactory, deployer }: Params) => {
return ethers.utils.defaultAbiCoder.encode(
["string", "string", "address", "address"],
[name, symbol, titleEscrowFactory, deployer]
);
export const encodeInitParams = ({ name, symbol, deployer }: Params) => {
return ethers.utils.defaultAbiCoder.encode(["string", "string", "address"], [name, symbol, deployer]);
};
9 changes: 4 additions & 5 deletions tasks/deploy-token.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// noinspection ExceptionCaughtLocallyJS

import { task } from "hardhat/config";
import { ImplDeployer, TradeTrustERC721 } from "@tradetrust/contracts";
import { DeploymentEvent } from "@tradetrust/contracts/ImplDeployer";
import { TDocDeployer, TradeTrustERC721 } from "@tradetrust/contracts";
import { DeploymentEvent } from "@tradetrust/contracts/TDocDeployer";
import { verifyContract, wait, deployContract, isSupportedTitleEscrowFactory } from "./helpers";
import { TASK_DEPLOY_TOKEN } from "./task-names";
import { constants } from "../src";
Expand Down Expand Up @@ -51,13 +51,12 @@ task(TASK_DEPLOY_TOKEN)
if (!deployerContractAddress || !implAddress) {
throw new Error(`Network ${network.name} currently is not supported. Use --standalone instead.`);
}
const deployerContract = (await ethers.getContractFactory("ImplDeployer")).attach(
const deployerContract = (await ethers.getContractFactory("TDocDeployer")).attach(
deployerContractAddress
) as ImplDeployer;
) as TDocDeployer;
const initParam = encodeInitParams({
name,
symbol,
titleEscrowFactory: factoryAddress,
deployer: deployerAddress,
});
const tx = await deployerContract.deploy(implAddress, initParam);
Expand Down
74 changes: 43 additions & 31 deletions test/ImplDeployer.test.ts → test/TDocDeployer.test.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,50 @@
import { ethers, waffle } from "hardhat";
import { ImplDeployer, TradeTrustERC721Impl } from "@tradetrust/contracts";
import { TDocDeployer, TradeTrustERC721Impl } from "@tradetrust/contracts";
import faker from "faker";
import { ContractTransaction } from "ethers";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { DeploymentEvent } from "@tradetrust/contracts/ImplDeployer";
import { DeploymentEvent } from "@tradetrust/contracts/TDocDeployer";
import { expect } from ".";
import { encodeInitParams, getEventFromReceipt } from "../src/utils";
import { defaultAddress, contractInterfaceId } from "../src/constants";
import { deployImplDeployerFixture, deployTradeTrustERC721ImplFixture } from "./fixtures";
import { deployTDocDeployerFixture, deployTradeTrustERC721ImplFixture } from "./fixtures";
import { getTestUsers, TestUsers } from "./helpers";

const { loadFixture } = waffle;

describe("ImplDeployer", async () => {
describe("TDocDeployer", async () => {
let users: TestUsers;
let deployer: SignerWithAddress;

let deployerContract: ImplDeployer;
let deployerContract: TDocDeployer;
let implContract: TradeTrustERC721Impl;
let fakeTitleEscrowFactory: string;

let deployerContractAsOwner: ImplDeployer;
let deployerContractAsNonOwner: ImplDeployer;
let deployerContractAsOwner: TDocDeployer;
let deployerContractAsNonOwner: TDocDeployer;

beforeEach(async () => {
users = await getTestUsers();
deployer = users.carrier;

fakeTitleEscrowFactory = ethers.utils.getAddress(faker.finance.ethereumAddress());

[implContract, deployerContract] = await Promise.all([
loadFixture(deployTradeTrustERC721ImplFixture({ deployer })),
loadFixture(deployImplDeployerFixture({ deployer })),
loadFixture(deployTDocDeployerFixture({ deployer })),
]);

deployerContractAsOwner = deployerContract.connect(deployer);
deployerContractAsNonOwner = deployerContract.connect(users.beneficiary);
});

describe("Deployer Implementation", () => {
let deployerImpl: ImplDeployer;
let deployerImpl: TDocDeployer;

beforeEach(async () => {
deployerImpl = (await (await ethers.getContractFactory("ImplDeployer"))
deployerImpl = (await (await ethers.getContractFactory("TDocDeployer"))
.connect(deployer)
.deploy()) as ImplDeployer;
.deploy()) as TDocDeployer;
});

it("should initialise deployer implementation", async () => {
Expand All @@ -58,10 +61,10 @@ describe("ImplDeployer", async () => {
});

describe("Upgrade Deployer", () => {
let mockDeployerImpl: ImplDeployer;
let mockDeployerImpl: TDocDeployer;

beforeEach(async () => {
mockDeployerImpl = (await (await ethers.getContractFactory("ImplDeployer")).deploy()) as ImplDeployer;
mockDeployerImpl = (await (await ethers.getContractFactory("TDocDeployer")).deploy()) as TDocDeployer;
});

it("should allow owner to upgrade", async () => {
Expand All @@ -86,86 +89,90 @@ describe("ImplDeployer", async () => {

describe("Adding Implementation", () => {
beforeEach(async () => {
await deployerContractAsOwner.addImplementation(implContract.address);
await deployerContractAsOwner.addImplementation(implContract.address, fakeTitleEscrowFactory);
});

it("should add implementation correctly", async () => {
const res = await deployerContractAsNonOwner.implementations(implContract.address);

expect(res).to.be.true;
expect(res).to.equal(fakeTitleEscrowFactory);
});

it("should not allow adding an already added implementation", async () => {
const tx = deployerContractAsOwner.addImplementation(implContract.address);
const tx = deployerContractAsOwner.addImplementation(implContract.address, fakeTitleEscrowFactory);

await expect(tx).to.be.revertedWith("ImplDeployer: Already added");
await expect(tx).to.be.revertedWith("TDocDeployer: Already added");
});

it("should not allow non-owner to add implementation", async () => {
const tx = deployerContractAsNonOwner.addImplementation(implContract.address);
const tx = deployerContractAsNonOwner.addImplementation(implContract.address, fakeTitleEscrowFactory);

await expect(tx).to.be.revertedWith("Ownable: caller is not the owner");
});
});

describe("Removing Implementation", () => {
it("should remove implementation correctly", async () => {
await deployerContractAsOwner.addImplementation(implContract.address);
await deployerContractAsOwner.addImplementation(implContract.address, fakeTitleEscrowFactory);
const initialRes = await deployerContract.implementations(implContract.address);

await deployerContractAsOwner.removeImplementation(implContract.address);
const currentRes = await deployerContract.implementations(implContract.address);

expect(initialRes).to.be.true;
expect(currentRes).to.be.false;
expect(initialRes).to.equal(fakeTitleEscrowFactory);
expect(currentRes).to.equal(defaultAddress.Zero);
});

it("should not allow non-owner to remove implementation", async () => {
const tx = deployerContractAsNonOwner.removeImplementation(implContract.address);

await expect(tx).to.be.revertedWith("Ownable: caller is not the owner");
});

it("should not allow removing an invalid implementation", async () => {
const fakeImplContract = faker.finance.ethereumAddress();

const tx = deployerContractAsOwner.removeImplementation(fakeImplContract);

await expect(tx).to.be.revertedWith("TDocDeployer: Invalid implementation");
});
});
});

describe("Deployment Behaviours", () => {
let fakeTokenName: string;
let fakeTokenSymbol: string;
let fakeTitleEscrowFactoryAddr: string;
let registryAdmin: SignerWithAddress;

beforeEach(async () => {
fakeTokenName = "The Great Shipping Co.";
fakeTokenSymbol = "GSC";
fakeTitleEscrowFactoryAddr = ethers.utils.getAddress(faker.finance.ethereumAddress());
registryAdmin = users.others[faker.datatype.number(users.others.length - 1)];

await deployerContractAsOwner.addImplementation(implContract.address);
await deployerContractAsOwner.addImplementation(implContract.address, fakeTitleEscrowFactory);
});

it("should not allow non-whitelisted implementations", async () => {
const fakeAddress = faker.finance.ethereumAddress();
const initParams = encodeInitParams({
name: fakeTokenName,
symbol: fakeTokenSymbol,
titleEscrowFactory: fakeTitleEscrowFactoryAddr,
deployer: registryAdmin.address,
});
const tx = deployerContractAsNonOwner.deploy(fakeAddress, initParams);

await expect(tx).to.be.revertedWith("ImplDeployer: Not whitelisted");
await expect(tx).to.be.revertedWith("TDocDeployer: Not whitelisted");
});

it("should revert when registry admin is zero address", async () => {
const initParams = encodeInitParams({
name: fakeTokenName,
symbol: fakeTokenSymbol,
titleEscrowFactory: fakeTitleEscrowFactoryAddr,
deployer: defaultAddress.Zero,
});
const tx = deployerContractAsNonOwner.deploy(implContract.address, initParams);

await expect(tx).to.be.revertedWith("ImplDeployer: Init fail");
await expect(tx).to.be.revertedWith("TDocDeployer: Init fail");
});

describe("Deploy", () => {
Expand All @@ -177,7 +184,6 @@ describe("ImplDeployer", async () => {
initParams = encodeInitParams({
name: fakeTokenName,
symbol: fakeTokenSymbol,
titleEscrowFactory: fakeTitleEscrowFactoryAddr,
deployer: registryAdmin.address,
});
createTx = await deployerContractAsNonOwner.deploy(implContract.address, initParams);
Expand Down Expand Up @@ -207,7 +213,7 @@ describe("ImplDeployer", async () => {
it("should initialise title escrow factory", async () => {
const res = await clonedRegistryContract.titleEscrowFactory();

expect(res).to.equal(fakeTitleEscrowFactoryAddr);
expect(res).to.equal(fakeTitleEscrowFactory);
});

it("should initialise deployer account as admin", async () => {
Expand Down Expand Up @@ -236,7 +242,13 @@ describe("ImplDeployer", async () => {
it("should emit Deployment event", async () => {
expect(createTx)
.to.emit(deployerContract, "Deployment")
.withArgs(clonedRegistryContract.address, implContract.address, initParams);
.withArgs(
clonedRegistryContract.address,
implContract.address,
users.beneficiary.address,
fakeTitleEscrowFactory,
initParams
);
});
});
});
Expand Down

0 comments on commit 174d52d

Please sign in to comment.