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

Add paid distributor #24

Merged
merged 4 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/orange-oranges-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@peeramid-labs/eds": minor
---

added paid distributor contract
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
"test": "export NODE_ENV=TEST && pnpm hardhat test",
"test:parallel": "export NODE_ENV=TEST && pnpm hardhat test --parallel",
"build": "pnpm hardhat compile",
"lint:sol": "prettier --loglevel warn --ignore-path .prettierignore '{src,test}/**/*.sol' --check && solhint '{contracts,test}/**/*.sol'",
"lint:sol:fix": "prettier --loglevel warn --ignore-path .prettierignore '{src,test}/**/*.sol' --write",
"lint:sol": "prettier --log-level warn --ignore-path .prettierignore '{src,test}/**/*.sol' --check && solhint '{contracts,test}/**/*.sol'",
"lint:sol:fix": "prettier --log-level warn --ignore-path .prettierignore '{src,test}/**/*.sol' --write",
"version": "pnpm lint:fix && pnpm build && changeset version",
"release": "pnpm build && changeset publish"
},
Expand Down
42 changes: 25 additions & 17 deletions src/abstracts/Distributor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
}

using EnumerableSet for EnumerableSet.Bytes32Set;
EnumerableSet.Bytes32Set private distirbutionsSet;
EnumerableSet.Bytes32Set private distributionsSet;
mapping(address instance => uint256 instanceId) private instanceIds;
mapping(uint256 instance => bytes32 distributorsId) public distributionOf;
mapping(bytes32 distributorsId => DistributionComponent distirbution) public distributionComponents;
Expand All @@ -38,7 +38,7 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
uint256 public numInstances;
// @inheritdoc IDistributor
function getDistributions() external view returns (bytes32[] memory) {
return distirbutionsSet.values();
return distributionsSet.values();
}
// @inheritdoc IDistributor
function getDistributionId(address instance) external view virtual returns (bytes32) {
Expand All @@ -58,36 +58,44 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
address repository,
address initializer,
LibSemver.VersionRequirement memory requirement
) internal {
) internal virtual returns (bytes32 distributorId) {
if (!ERC165Checker.supportsInterface(address(repository), type(IRepository).interfaceId)) {
revert InvalidRepository(repository);
}
bytes32 distributorId = keccak256(abi.encode(repository, initializer));
distributorId = keccak256(abi.encode(repository, initializer));
if (LibSemver.toUint256(requirement.version) == 0) {
revert InvalidVersionRequested(distributorId, LibSemver.toString(requirement.version));
}
_newDistriubutionRecord(distributorId, repository, initializer);
_newDistributionRecord(distributorId, repository, initializer);
versionRequirements[distributorId] = requirement;
emit VersionChanged(distributorId, requirement, requirement);
}

function _newDistriubutionRecord(bytes32 distributorId, address source, address initializer) private {
if (distirbutionsSet.contains(distributorId)) revert DistributionExists(distributorId);
distirbutionsSet.add(distributorId);
function calculateDistributorId(address repository, address initializer) public pure returns (bytes32) {
return keccak256(abi.encode(repository, initializer));
}

function calculateDistributorId(bytes32 sourceId, address initializer) public pure returns (bytes32) {
return keccak256(abi.encode(sourceId, initializer));
}

function _newDistributionRecord(bytes32 distributorId, address source, address initializer) private {
if (distributionsSet.contains(distributorId)) revert DistributionExists(distributorId);
distributionsSet.add(distributorId);
distributionComponents[distributorId] = DistributionComponent(source, initializer);
emit DistributionAdded(distributorId, source, initializer);
}
function _addDistribution(bytes32 id, address initializerAddress) internal {
function _addDistribution(bytes32 id, address initializerAddress) internal virtual returns (bytes32 distributorId) {
ICodeIndex codeIndex = getContractsIndex();
address distributionLocation = codeIndex.get(id);
if (distributionLocation == address(0)) revert DistributionNotFound(id);
bytes32 distributorsId = keccak256(abi.encode(id, initializerAddress));
_newDistriubutionRecord(distributorsId, distributionLocation, initializerAddress);
distributorId = keccak256(abi.encode(id, initializerAddress));
_newDistributionRecord(distributorId, distributionLocation, initializerAddress);
}

function _removeDistribution(bytes32 distributorsId) internal virtual {
if (!distirbutionsSet.contains(distributorsId)) revert DistributionNotFound(distributorsId);
distirbutionsSet.remove(distributorsId);
if (!distributionsSet.contains(distributorsId)) revert DistributionNotFound(distributorsId);
distributionsSet.remove(distributorsId);
delete distributionComponents[distributorsId];
delete versionRequirements[distributorsId];
emit DistributionRemoved(distributorsId);
Expand All @@ -101,7 +109,7 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
bytes32 distributorsId,
bytes memory args
) internal virtual returns (address[] memory instances, bytes32 distributionName, uint256 distributionVersion) {
if (!distirbutionsSet.contains(distributorsId)) revert DistributionNotFound(distributorsId);
if (!distributionsSet.contains(distributorsId)) revert DistributionNotFound(distributorsId);
DistributionComponent memory distributionComponent = distributionComponents[distributorsId];
LibSemver.VersionRequirement memory versionRequirement = versionRequirements[distributorsId];

Expand Down Expand Up @@ -182,7 +190,7 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
if (
distributorsId != bytes32(0) &&
getInstanceId(target) == instanceId &&
distirbutionsSet.contains(distributorsId)
distributionsSet.contains(distributorsId)
) {
// ToDo: This check could be based on DistributionOf, hence allowing cross-instance calls
// Use layerConfig to allow client to configure requirement for the call
Expand All @@ -209,7 +217,7 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
address target = config.length > 0 ? abi.decode(config, (address)) : msg.sender;
bytes32 distributorsId = distributionOf[getInstanceId(maybeInstance)];
uint256 instanceId = getInstanceId(maybeInstance);
if ((getInstanceId(target) != getInstanceId(maybeInstance)) && distirbutionsSet.contains(distributorsId)) {
if ((getInstanceId(target) != getInstanceId(maybeInstance)) && distributionsSet.contains(distributorsId)) {
revert InvalidInstance(maybeInstance);
}
if (!LibSemver.compare(instanceVersions[instanceId], versionRequirements[distributorsId])) {
Expand All @@ -222,7 +230,7 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
}

function _changeVersion(bytes32 distributionId, LibSemver.VersionRequirement memory newRequirement) internal {
if (!distirbutionsSet.contains(distributionId)) revert DistributionNotFound(distributionId);
if (!distributionsSet.contains(distributionId)) revert DistributionNotFound(distributionId);
LibSemver.VersionRequirement memory oldRequirement = versionRequirements[distributionId];
if (LibSemver.toUint256(oldRequirement.version) == 0) {
revert UniversionedDistribution(distributionId);
Expand Down
57 changes: 57 additions & 0 deletions src/abstracts/TokenizedDistributor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import "./Distributor.sol";

abstract contract TokenizedDistributor is Distributor {
event InstantiationCostChanged(bytes32 indexed id, uint256 cost);
IERC20 public paymentToken;
address public _beneficiary;
mapping(bytes32 id => uint256) public instantiationCosts;
uint256 public defaultInstantiationCost;
constructor(IERC20 token, uint256 defaultCost, address beneficiary) Distributor() {
paymentToken = token;
defaultInstantiationCost = defaultCost;
_beneficiary = beneficiary;
}

/**
* @notice Sets instantiation cost on a specific instantiation id
* @param distributorsId distributors id
* @param cost cost of instantiation
*/
function _setInstantiationCost(bytes32 distributorsId, uint256 cost) internal {
instantiationCosts[distributorsId] = cost;
emit InstantiationCostChanged(distributorsId, cost);
}

/**
* @inheritdoc Distributor
*/
function _addDistribution(
bytes32 id,
address initializerAddress
) internal override returns (bytes32 distributorsId) {
distributorsId = super._addDistribution(id, initializerAddress);
_setInstantiationCost(distributorsId, defaultInstantiationCost);
}

/**
* @inheritdoc Distributor
*/
function _instantiate(
bytes32 distributorsId,
bytes memory args
)
internal
virtual
override
returns (address[] memory instances, bytes32 distributionName, uint256 distributionVersion)
{
paymentToken.transferFrom(msg.sender, _beneficiary, instantiationCosts[distributorsId]);
return super._instantiate(distributorsId, args);
}
}
10 changes: 10 additions & 0 deletions src/mocks/MockERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockERC20 is ERC20 {
constructor(string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
}
}
94 changes: 94 additions & 0 deletions src/mocks/MockTokenizedDistributor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

import "../abstracts/TokenizedDistributor.sol";
import "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract MockTokenizedDistributor is TokenizedDistributor, AccessControlDefaultAdminRules {
constructor(
address defaultAdmin,
IERC20 token,
uint256 defaultCost
) TokenizedDistributor(token, defaultCost, defaultAdmin) AccessControlDefaultAdminRules(3 days, defaultAdmin) {
paymentToken = token;
defaultInstantiationCost = defaultCost;
}

/**
* @notice Adds a new distribution with the given identifier and initializer address.
* @dev This function can only be called by an account with the `DEFAULT_ADMIN_ROLE`.
* @param id The unique identifier for the distribution.
* @param initializer The address that initializes the distribution.
*/
function addDistribution(bytes32 id, address initializer) external onlyRole(DEFAULT_ADMIN_ROLE) {
super._addDistribution(id, initializer);
instantiationCosts[keccak256(abi.encode(id, initializer))] = defaultInstantiationCost;
}

/**
* @notice Sets instantiation cost on a specific instantiation id
* @param id distributors id
* @param cost cost of instantiation
*/
function setInstantiationCost(bytes32 id, uint256 cost) public onlyRole(DEFAULT_ADMIN_ROLE) {
super._setInstantiationCost(id, cost);
}

/**
* @notice Instantiates a new contract with the given identifier and arguments.
* @param id The unique identifier for the contract to be instantiated.
* @param args The calldata arguments required for the instantiation process.
* @return srcs An array of instantiated infrastructure
* @return name The name of the instantiated distribution.
* @return version The version number of the instantiated distribution.
*/
function instantiate(
bytes32 id,
bytes calldata args
) external returns (address[] memory srcs, bytes32 name, uint256 version) {
return super._instantiate(id, args);
}

/**
* @notice Removes a distribution entry identified by the given ID.
* @dev This function can only be called by an account with the `DEFAULT_ADMIN_ROLE`.
* @param id The unique identifier of the distribution entry to be removed.
*/
function removeDistribution(bytes32 id) public onlyRole(DEFAULT_ADMIN_ROLE) {
_removeDistribution(id);
}

/**
*
* This function checks if the contract implements the interface defined by ERC165
*
* @param interfaceId The interface identifier, as specified in ERC-165.
* @return `true` if the contract implements `interfaceId` and
* `interfaceId` is not 0xffffffff, `false` otherwise.
*/
function supportsInterface(
bytes4 interfaceId
) public view virtual override(AccessControlDefaultAdminRules, Distributor) returns (bool) {
return
AccessControlDefaultAdminRules.supportsInterface(interfaceId) || Distributor.supportsInterface(interfaceId);
}

function changeVersion(
bytes32 distributionId,
LibSemver.VersionRequirement memory newRequirement
) public override onlyRole(DEFAULT_ADMIN_ROLE) {
super._changeVersion(distributionId, newRequirement);
}

// @inheritdoc IDistributor
function addDistribution(
IRepository repository,
address initializer,
LibSemver.VersionRequirement memory requirement
) external override onlyRole(DEFAULT_ADMIN_ROLE) {
bytes32 distributorId = keccak256(abi.encode(repository, initializer));
instantiationCosts[distributorId] = defaultInstantiationCost;
super._addDistribution(address(repository), initializer, requirement);
}
}
5 changes: 0 additions & 5 deletions test/eds/CloneDistribution.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { ethers } from "hardhat";
import { Signer } from "ethers";
import { expect } from "chai";

describe("CloneDistribution", function () {
let cloneDistribution: any;
let owner: Signer;
let addr1: Signer;
let addr2: Signer;

beforeEach(async function () {
const CloneDistribution = await ethers.getContractFactory("MockCloneDistribution");
[owner, addr1, addr2] = await ethers.getSigners();
cloneDistribution = await CloneDistribution.deploy();
await cloneDistribution.deployed();
});
Expand Down
2 changes: 1 addition & 1 deletion test/eds/CodeHashDistribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe("CloneHashDistribution", function () {
0
)) as CodeHashDistribution;

const { src, name, version } = await codeHashDistribution.get();
const { name, version } = await codeHashDistribution.get();
expect(ethers.utils.parseBytes32String(name)).to.be.equal("testDistribution");
expect(version).to.be.equal(0);
});
Expand Down
Loading
Loading