From 0d6b9f9a33726b90f816f6203f2f6d1d965455e9 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Fri, 17 May 2024 11:17:10 -0400 Subject: [PATCH 001/105] Initial commit From 658c1499a17376700d3d2af5e6ba7e987ad66619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Wed, 10 Apr 2024 16:44:53 +0200 Subject: [PATCH 002/105] Add simple hooks to GlobalAllowList to allow simple extensibility --- .../contracts/coordination/GlobalAllowList.sol | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/contracts/contracts/coordination/GlobalAllowList.sol b/contracts/contracts/coordination/GlobalAllowList.sol index 3d8bc9759..b8d3718e0 100644 --- a/contracts/contracts/coordination/GlobalAllowList.sol +++ b/contracts/contracts/coordination/GlobalAllowList.sol @@ -43,16 +43,30 @@ contract GlobalAllowList is IEncryptionAuthorizer { return authorizations[lookupKey(ritualId, encryptor)]; } + function _beforeIsAuthorized( + uint32 ritualId, + bytes memory evidence, + bytes memory ciphertextHeader + ) internal view virtual {} + function isAuthorized( uint32 ritualId, bytes memory evidence, bytes memory ciphertextHeader ) external view override returns (bool) { + _beforeIsAuthorized(ritualId, evidence, ciphertextHeader); + bytes32 digest = keccak256(ciphertextHeader); address recoveredAddress = digest.toEthSignedMessageHash().recover(evidence); return isAddressAuthorized(ritualId, recoveredAddress); } + function _beforeSetAuthorization( + uint32 ritualId, + address[] calldata addresses, + bool value + ) internal view virtual {} + function authorize( uint32 ritualId, address[] calldata addresses @@ -69,6 +83,9 @@ contract GlobalAllowList is IEncryptionAuthorizer { function setAuthorizations(uint32 ritualId, address[] calldata addresses, bool value) internal { require(coordinator.isRitualActive(ritualId), "Only active rituals can add authorizations"); + + _beforeSetAuthorization(ritualId, addresses, value); + for (uint256 i = 0; i < addresses.length; i++) { authorizations[lookupKey(ritualId, addresses[i])] = value; emit AddressAuthorizationSet(ritualId, addresses[i], value); From 99706a0364415a22b368565a88775cc80cb1aaed Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Mon, 15 Apr 2024 12:25:48 +0200 Subject: [PATCH 003/105] wip: managed allow list --- .../coordination/ManagedAllowList.sol | 77 +++++++++++++++++++ scripts/deploy_managed_allow_list.py | 26 +++++++ scripts/initiate_ritual.py | 2 +- 3 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 contracts/contracts/coordination/ManagedAllowList.sol create mode 100644 scripts/deploy_managed_allow_list.py diff --git a/contracts/contracts/coordination/ManagedAllowList.sol b/contracts/contracts/coordination/ManagedAllowList.sol new file mode 100644 index 000000000..a7a314db2 --- /dev/null +++ b/contracts/contracts/coordination/ManagedAllowList.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "./IEncryptionAuthorizer.sol"; +import "./Coordinator.sol"; + +contract ManagedAllowList is IEncryptionAuthorizer { + using MessageHashUtils for bytes32; + using ECDSA for bytes32; + + Coordinator public immutable coordinator; + + mapping(address => bool) public administrators; + mapping(address => uint256) public administratorCaps; + mapping(bytes32 => bool) internal authorizations; + + event AdministratorAdded(address indexed admin); + event AdministratorRemoved(address indexed admin); + event EncryptorAdded(uint32 indexed ritualId, address indexed encryptor); + event EncryptorRemoved(uint32 indexed ritualId, address indexed encryptor); + + constructor(Coordinator _coordinator) { + require(address(_coordinator) != address(0), "Coordinator cannot be zero address"); + require(_coordinator.numberOfRituals() >= 0, "Invalid coordinator"); + coordinator = _coordinator; + } + + modifier onlyAuthority(uint32 ritualId) { + require(coordinator.getAuthority(ritualId) == msg.sender, "Only ritual authority is permitted"); + _; + } + + modifier onlyAdministrator() { + require(administrators[msg.sender], "Only administrator is permitted"); + _; + } + + function addAdministrator(uint32 ritualId, address admin) external onlyAuthority(ritualId) { + administrators[admin] = true; + emit AdministratorAdded(admin); + } + + function removeAdministrator(uint32 ritualId, address admin) external onlyAuthority(ritualId) { + administrators[admin] = false; + emit AdministratorRemoved(admin); + } + + function setAdministratorCap(uint32 ritualId, address admin, uint256 cap) external onlyAuthority(ritualId) { + administratorCaps[admin] = cap; + } + + function addEncryptor(uint32 ritualId, address encryptor) external onlyAdministrator { + require(administratorCaps[msg.sender] > 0, "Administrator cap reached"); + authorizations[keccak256(abi.encodePacked(ritualId, encryptor))] = true; + administratorCaps[msg.sender]--; + emit EncryptorAdded(ritualId, encryptor); + } + + function removeEncryptor(uint32 ritualId, address encryptor) external onlyAdministrator { + authorizations[keccak256(abi.encodePacked(ritualId, encryptor))] = false; + administratorCaps[msg.sender]++; + emit EncryptorRemoved(ritualId, encryptor); + } + + function isAuthorized( + uint32 ritualId, + bytes memory evidence, + bytes memory ciphertextHeader + ) external view override returns (bool) { + bytes32 digest = keccak256(ciphertextHeader); + address recoveredAddress = digest.toEthSignedMessageHash().recover(evidence); + return authorizations[keccak256(abi.encodePacked(ritualId, recoveredAddress))]; + } +} diff --git a/scripts/deploy_managed_allow_list.py b/scripts/deploy_managed_allow_list.py new file mode 100644 index 000000000..a0a65de56 --- /dev/null +++ b/scripts/deploy_managed_allow_list.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 + +from ape import project + +from deployment.constants import ARTIFACTS_DIR, CONSTRUCTOR_PARAMS_DIR +from deployment.params import Deployer +from deployment.registry import merge_registries + +VERIFY = False +CONSTRUCTOR_PARAMS_FILEPATH = CONSTRUCTOR_PARAMS_DIR / "managed_allow_list.yml" +LYNX = ARTIFACTS_DIR / "lynx.json" +TAPIR = ARTIFACTS_DIR / "tapir.json" + + +def main(): + deployer = Deployer.from_yaml(filepath=CONSTRUCTOR_PARAMS_FILEPATH, verify=VERIFY) + mnaged_allow_list = deployer.deploy(project.ManagedAllowList) + deployments = [mnaged_allow_list] + deployer.finalize(deployments=deployments) + + for domain in (LYNX, TAPIR): + merge_registries( + registry_1_filepath=domain, + registry_2_filepath=deployer.registry_filepath, + output_filepath=domain, + ) diff --git a/scripts/initiate_ritual.py b/scripts/initiate_ritual.py index 1fd5e021a..64d23338a 100644 --- a/scripts/initiate_ritual.py +++ b/scripts/initiate_ritual.py @@ -32,7 +32,7 @@ "--access-controller", "-a", help="global allow list or open access authorizer.", - type=click.Choice(["GlobalAllowList", "OpenAccessAuthorizer"]), + type=click.Choice(["GlobalAllowList", "OpenAccessAuthorizer", "ManagedAllowList"]), required=True, ) def cli(domain, duration, network, account, access_controller): From da362094550f229e4e3064ade392930891073f76 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Mon, 15 Apr 2024 15:51:00 +0200 Subject: [PATCH 004/105] wip: add some tests --- .../coordination/ManagedAllowList.sol | 15 +- tests/test_managed_allow_list.py | 164 ++++++++++++++++++ 2 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 tests/test_managed_allow_list.py diff --git a/contracts/contracts/coordination/ManagedAllowList.sol b/contracts/contracts/coordination/ManagedAllowList.sol index a7a314db2..ee52bd36e 100644 --- a/contracts/contracts/coordination/ManagedAllowList.sol +++ b/contracts/contracts/coordination/ManagedAllowList.sol @@ -29,7 +29,10 @@ contract ManagedAllowList is IEncryptionAuthorizer { } modifier onlyAuthority(uint32 ritualId) { - require(coordinator.getAuthority(ritualId) == msg.sender, "Only ritual authority is permitted"); + require( + coordinator.getAuthority(ritualId) == msg.sender, + "Only ritual authority is permitted" + ); _; } @@ -48,7 +51,11 @@ contract ManagedAllowList is IEncryptionAuthorizer { emit AdministratorRemoved(admin); } - function setAdministratorCap(uint32 ritualId, address admin, uint256 cap) external onlyAuthority(ritualId) { + function setAdministratorCap( + uint32 ritualId, + address admin, + uint256 cap + ) external onlyAuthority(ritualId) { administratorCaps[admin] = cap; } @@ -65,6 +72,10 @@ contract ManagedAllowList is IEncryptionAuthorizer { emit EncryptorRemoved(ritualId, encryptor); } + function isEncryptor(uint32 ritualId, address encryptor) external view returns (bool) { + return authorizations[keccak256(abi.encodePacked(ritualId, encryptor))]; + } + function isAuthorized( uint32 ritualId, bytes memory evidence, diff --git a/tests/test_managed_allow_list.py b/tests/test_managed_allow_list.py new file mode 100644 index 000000000..ccbfe5211 --- /dev/null +++ b/tests/test_managed_allow_list.py @@ -0,0 +1,164 @@ +import pytest + +# Stuff copied from test_coordinator.py + +TIMEOUT = 1000 +MAX_DKG_SIZE = 31 +FEE_RATE = 42 +ERC20_SUPPLY = 10**24 + + +@pytest.fixture(scope="module") +def nodes(accounts): + return sorted(accounts[:MAX_DKG_SIZE], key=lambda x: x.address.lower()) + + +@pytest.fixture(scope="module") +def initiator(accounts): + initiator_index = MAX_DKG_SIZE + 1 + assert len(accounts) >= initiator_index + return accounts[initiator_index] + + +@pytest.fixture(scope="module") +def deployer(accounts): + deployer_index = MAX_DKG_SIZE + 2 + assert len(accounts) >= deployer_index + return accounts[deployer_index] + + +@pytest.fixture(scope="module") +def treasury(accounts): + treasury_index = MAX_DKG_SIZE + 3 + assert len(accounts) >= treasury_index + return accounts[treasury_index] + + +@pytest.fixture() +def application(project, deployer, nodes): + contract = project.ChildApplicationForCoordinatorMock.deploy(sender=deployer) + for n in nodes: + contract.updateOperator(n, n, sender=deployer) + contract.updateAuthorization(n, 42, sender=deployer) + return contract + + +@pytest.fixture() +def erc20(project, initiator): + token = project.TestToken.deploy(ERC20_SUPPLY, sender=initiator) + return token + + +@pytest.fixture() +def coordinator(project, deployer, application, erc20, initiator, oz_dependency): + contract = project.Coordinator.deploy( + application.address, + erc20.address, + FEE_RATE, + sender=deployer, + ) + + encoded_initializer_function = contract.initialize.encode_input(TIMEOUT, MAX_DKG_SIZE, deployer) + proxy = oz_dependency.TransparentUpgradeableProxy.deploy( + contract.address, + deployer, + encoded_initializer_function, + sender=deployer, + ) + proxy_contract = project.Coordinator.at(proxy.address) + + proxy_contract.grantRole(contract.INITIATOR_ROLE(), initiator, sender=deployer) + return proxy_contract + + +# My fixtures + +RITUAL_ID = 1 +ENCRYPTOR_CAP = 5 + + +@pytest.fixture() +def authority(accounts): + return accounts[1] + + +@pytest.fixture() +def admin(accounts): + return accounts[2] + + +@pytest.fixture() +def encryptor(accounts): + return accounts[3] + + +@pytest.fixture() +def managed_allow_list(project, coordinator, authority): + return project.ManagedAllowList.deploy(coordinator.address, sender=authority) + + +def test_initial_parameters(managed_allow_list, coordinator, authority, admin, encryptor): + assert managed_allow_list.coordinator() == coordinator.address + assert managed_allow_list.administratorCaps(admin.address) == 0 + assert not managed_allow_list.administrators(admin.address) + assert not managed_allow_list.isEncryptor(encryptor.address) + + +def test_add_administrator(managed_allow_list, authority, admin): + managed_allow_list.addAdministrator(RITUAL_ID, admin, sender=authority) + assert managed_allow_list.administrators(admin) + + +def test_remove_administrator(managed_allow_list, authority, admin): + managed_allow_list.addAdministrator(RITUAL_ID, admin, sender=authority) + managed_allow_list.removeAdministrator(RITUAL_ID, admin, sender=authority) + assert not managed_allow_list.administrators(admin) + + +def test_set_administrator_cap(managed_allow_list, authority, admin): + managed_allow_list.setAdministratorCap(RITUAL_ID, admin, ENCRYPTOR_CAP, sender=authority) + assert managed_allow_list.administratorCaps(admin) == ENCRYPTOR_CAP + + +def test_add_encryptor(managed_allow_list, authority, admin, encryptor): + managed_allow_list.setAdministratorCap(RITUAL_ID, admin, ENCRYPTOR_CAP, sender=authority) + managed_allow_list.addEncryptor(RITUAL_ID, encryptor, sender=admin) + assert managed_allow_list.isEncryptor(encryptor) + + +def test_remove_encryptor(managed_allow_list, admin, authority, encryptor): + managed_allow_list.setAdministratorCap(RITUAL_ID, admin, ENCRYPTOR_CAP, sender=authority) + managed_allow_list.addEncryptor(RITUAL_ID, encryptor, sender=admin) + managed_allow_list.removeEncryptor(RITUAL_ID, encryptor, sender=admin) + assert not managed_allow_list.isEncryptor(encryptor) + + +def test_only_authority_can_add_administrator(managed_allow_list, admin, authority, encryptor): + with pytest.raises(Exception): + managed_allow_list.addAdministrator(RITUAL_ID, admin, sender=authority) + managed_allow_list.addAdministrator(RITUAL_ID, admin, sender=authority) + assert managed_allow_list.administrators(admin) + + +def test_only_authority_can_remove_administrator(managed_allow_list, admin, authority): + managed_allow_list.addAdministrator(RITUAL_ID, admin, sender=authority) + with pytest.raises(Exception): + managed_allow_list.removeAdministrator(RITUAL_ID, admin, sender=authority) + managed_allow_list.removeAdministrator(RITUAL_ID, admin, sender=authority) + assert not managed_allow_list.administrators(admin) + + +def test_only_administrator_can_add_encryptor(managed_allow_list, admin, authority, encryptor): + managed_allow_list.setAdministratorCap(RITUAL_ID, admin, ENCRYPTOR_CAP, sender=authority) + with pytest.raises(Exception): + managed_allow_list.addEncryptor(RITUAL_ID, encryptor, sender=encryptor) + managed_allow_list.addEncryptor(RITUAL_ID, admin, sender=admin) + assert managed_allow_list.isEncryptor(admin) + + +def test_only_administrator_can_remove_encryptor(managed_allow_list, admin, authority, encryptor): + managed_allow_list.setAdministratorCap(RITUAL_ID, admin, ENCRYPTOR_CAP, sender=authority) + managed_allow_list.addEncryptor(RITUAL_ID, encryptor, sender=admin) + with pytest.raises(Exception): + managed_allow_list.removeEncrypt(RITUAL_ID, encryptor, sender=encryptor) + assert not managed_allow_list.isEncryptor(encryptor) From cf2e2c4056aade66d7d753589a3b4f19bfc533a4 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Mon, 15 Apr 2024 17:58:51 +0200 Subject: [PATCH 005/105] wip: change of plans wip: change of plans was a mistakerino --- .../coordination/GlobalAllowList.sol | 2 +- .../coordination/ManagedAllowList.sol | 94 ++++++++++++------- tests/test_coordinator.py | 2 +- tests/test_managed_allow_list.py | 64 ++++++------- 4 files changed, 91 insertions(+), 71 deletions(-) diff --git a/contracts/contracts/coordination/GlobalAllowList.sol b/contracts/contracts/coordination/GlobalAllowList.sol index b8d3718e0..dbfc866c1 100644 --- a/contracts/contracts/coordination/GlobalAllowList.sol +++ b/contracts/contracts/coordination/GlobalAllowList.sol @@ -82,7 +82,7 @@ contract GlobalAllowList is IEncryptionAuthorizer { } function setAuthorizations(uint32 ritualId, address[] calldata addresses, bool value) internal { - require(coordinator.isRitualActive(ritualId), "Only active rituals can add authorizations"); + require(coordinator.isRitualActive(ritualId), "Only active rituals can set authorizations"); _beforeSetAuthorization(ritualId, addresses, value); diff --git a/contracts/contracts/coordination/ManagedAllowList.sol b/contracts/contracts/coordination/ManagedAllowList.sol index ee52bd36e..120b13615 100644 --- a/contracts/contracts/coordination/ManagedAllowList.sol +++ b/contracts/contracts/coordination/ManagedAllowList.sol @@ -13,14 +13,15 @@ contract ManagedAllowList is IEncryptionAuthorizer { Coordinator public immutable coordinator; - mapping(address => bool) public administrators; - mapping(address => uint256) public administratorCaps; - mapping(bytes32 => bool) internal authorizations; + mapping(bytes32 => uint256) public administrators; // TODO: Rename to allowances? + mapping(bytes32 => bool) public authorizations; - event AdministratorAdded(address indexed admin); - event AdministratorRemoved(address indexed admin); - event EncryptorAdded(uint32 indexed ritualId, address indexed encryptor); - event EncryptorRemoved(uint32 indexed ritualId, address indexed encryptor); + event AdministratorCapSet(uint32 indexed ritualId, address indexed _address, uint256 cap); + event AddressAuthorizationSet( + uint32 indexed ritualId, + address indexed _address, + bool isAuthorized + ); constructor(Coordinator _coordinator) { require(address(_coordinator) != address(0), "Coordinator cannot be zero address"); @@ -28,6 +29,10 @@ contract ManagedAllowList is IEncryptionAuthorizer { coordinator = _coordinator; } + function lookupKey(uint32 ritualId, address encryptorOrAdmin) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(ritualId, encryptorOrAdmin)); + } + modifier onlyAuthority(uint32 ritualId) { require( coordinator.getAuthority(ritualId) == msg.sender, @@ -36,44 +41,43 @@ contract ManagedAllowList is IEncryptionAuthorizer { _; } - modifier onlyAdministrator() { - require(administrators[msg.sender], "Only administrator is permitted"); + modifier onlyAdministrator(uint32 ritualId) { + require( + administrators[lookupKey(ritualId, msg.sender)] > 9, + "Only administrator is permitted" + ); _; } - function addAdministrator(uint32 ritualId, address admin) external onlyAuthority(ritualId) { - administrators[admin] = true; - emit AdministratorAdded(admin); - } - - function removeAdministrator(uint32 ritualId, address admin) external onlyAuthority(ritualId) { - administrators[admin] = false; - emit AdministratorRemoved(admin); + function setAdministratorCaps( + uint32 ritualId, + address[] calldata addresses, + uint256 value + ) internal { + require(coordinator.isRitualActive(ritualId), "Only active rituals can set administrator caps"); + for (uint256 i = 0; i < addresses.length; i++) { + administrators[lookupKey(ritualId, addresses[i])] = value; + emit AdministratorCapSet(ritualId, addresses[i], value); + } } - function setAdministratorCap( + function addAdministrators( uint32 ritualId, - address admin, + address[] calldata addresses, uint256 cap ) external onlyAuthority(ritualId) { - administratorCaps[admin] = cap; + setAdministratorCaps(ritualId, addresses, cap); } - function addEncryptor(uint32 ritualId, address encryptor) external onlyAdministrator { - require(administratorCaps[msg.sender] > 0, "Administrator cap reached"); - authorizations[keccak256(abi.encodePacked(ritualId, encryptor))] = true; - administratorCaps[msg.sender]--; - emit EncryptorAdded(ritualId, encryptor); - } - - function removeEncryptor(uint32 ritualId, address encryptor) external onlyAdministrator { - authorizations[keccak256(abi.encodePacked(ritualId, encryptor))] = false; - administratorCaps[msg.sender]++; - emit EncryptorRemoved(ritualId, encryptor); + function removeAdministrators( + uint32 ritualId, + address[] calldata addresses + ) external onlyAuthority(ritualId) { + setAdministratorCaps(ritualId, addresses, 0); } - function isEncryptor(uint32 ritualId, address encryptor) external view returns (bool) { - return authorizations[keccak256(abi.encodePacked(ritualId, encryptor))]; + function isAddressAuthorized(uint32 ritualId, address encryptor) public view returns (bool) { + return authorizations[lookupKey(ritualId, encryptor)]; } function isAuthorized( @@ -83,6 +87,28 @@ contract ManagedAllowList is IEncryptionAuthorizer { ) external view override returns (bool) { bytes32 digest = keccak256(ciphertextHeader); address recoveredAddress = digest.toEthSignedMessageHash().recover(evidence); - return authorizations[keccak256(abi.encodePacked(ritualId, recoveredAddress))]; + return isAddressAuthorized(ritualId, recoveredAddress); + } + + function authorize( + uint32 ritualId, + address[] calldata addresses + ) external onlyAdministrator(ritualId) { + setAuthorizations(ritualId, addresses, true); + } + + function deauthorize( + uint32 ritualId, + address[] calldata addresses + ) external onlyAdministrator(ritualId) { + setAuthorizations(ritualId, addresses, false); + } + + function setAuthorizations(uint32 ritualId, address[] calldata addresses, bool value) internal { + require(coordinator.isRitualActive(ritualId), "Only active rituals can set authorizations"); + for (uint256 i = 0; i < addresses.length; i++) { + authorizations[lookupKey(ritualId, addresses[i])] = value; + emit AddressAuthorizationSet(ritualId, addresses[i], value); + } } } diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index 393db884a..809eb0f55 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -564,7 +564,7 @@ def test_authorize_using_global_allow_list( with ape.reverts("Only ritual authority is permitted"): global_allow_list.authorize(0, [deployer.address], sender=deployer) - with ape.reverts("Only active rituals can add authorizations"): + with ape.reverts("Only active rituals can set authorizations"): global_allow_list.authorize(0, [deployer.address], sender=initiator) with ape.reverts("Ritual not active"): diff --git a/tests/test_managed_allow_list.py b/tests/test_managed_allow_list.py index ccbfe5211..376860c9e 100644 --- a/tests/test_managed_allow_list.py +++ b/tests/test_managed_allow_list.py @@ -74,7 +74,7 @@ def coordinator(project, deployer, application, erc20, initiator, oz_dependency) # My fixtures RITUAL_ID = 1 -ENCRYPTOR_CAP = 5 +ADMIN_CAP = 5 @pytest.fixture() @@ -99,66 +99,60 @@ def managed_allow_list(project, coordinator, authority): def test_initial_parameters(managed_allow_list, coordinator, authority, admin, encryptor): assert managed_allow_list.coordinator() == coordinator.address - assert managed_allow_list.administratorCaps(admin.address) == 0 assert not managed_allow_list.administrators(admin.address) - assert not managed_allow_list.isEncryptor(encryptor.address) + assert not managed_allow_list.authorizations(encryptor.address) def test_add_administrator(managed_allow_list, authority, admin): - managed_allow_list.addAdministrator(RITUAL_ID, admin, sender=authority) - assert managed_allow_list.administrators(admin) + managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) + assert managed_allow_list.administrators(admin) == ADMIN_CAP def test_remove_administrator(managed_allow_list, authority, admin): - managed_allow_list.addAdministrator(RITUAL_ID, admin, sender=authority) - managed_allow_list.removeAdministrator(RITUAL_ID, admin, sender=authority) + managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) + managed_allow_list.removeAdministrators(RITUAL_ID, [admin], sender=authority) assert not managed_allow_list.administrators(admin) -def test_set_administrator_cap(managed_allow_list, authority, admin): - managed_allow_list.setAdministratorCap(RITUAL_ID, admin, ENCRYPTOR_CAP, sender=authority) - assert managed_allow_list.administratorCaps(admin) == ENCRYPTOR_CAP +def test_authorize(managed_allow_list, authority, admin, encryptor): + managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) + managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=admin) + assert managed_allow_list.authorizations(encryptor) -def test_add_encryptor(managed_allow_list, authority, admin, encryptor): - managed_allow_list.setAdministratorCap(RITUAL_ID, admin, ENCRYPTOR_CAP, sender=authority) - managed_allow_list.addEncryptor(RITUAL_ID, encryptor, sender=admin) - assert managed_allow_list.isEncryptor(encryptor) - - -def test_remove_encryptor(managed_allow_list, admin, authority, encryptor): - managed_allow_list.setAdministratorCap(RITUAL_ID, admin, ENCRYPTOR_CAP, sender=authority) - managed_allow_list.addEncryptor(RITUAL_ID, encryptor, sender=admin) - managed_allow_list.removeEncryptor(RITUAL_ID, encryptor, sender=admin) - assert not managed_allow_list.isEncryptor(encryptor) +def test_deauthorize(managed_allow_list, admin, authority, encryptor): + managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) + managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=admin) + managed_allow_list.deauthorize(RITUAL_ID, [encryptor], sender=admin) + assert not managed_allow_list.authorizations(encryptor) def test_only_authority_can_add_administrator(managed_allow_list, admin, authority, encryptor): with pytest.raises(Exception): - managed_allow_list.addAdministrator(RITUAL_ID, admin, sender=authority) - managed_allow_list.addAdministrator(RITUAL_ID, admin, sender=authority) + managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=admin) + managed_allow_list.addAdministrators(RITUAL_ID, [admin], sender=authority) assert managed_allow_list.administrators(admin) def test_only_authority_can_remove_administrator(managed_allow_list, admin, authority): managed_allow_list.addAdministrator(RITUAL_ID, admin, sender=authority) with pytest.raises(Exception): - managed_allow_list.removeAdministrator(RITUAL_ID, admin, sender=authority) - managed_allow_list.removeAdministrator(RITUAL_ID, admin, sender=authority) + managed_allow_list.removeAdministrators(RITUAL_ID, [admin], sender=admin) + managed_allow_list.removeAdministrators(RITUAL_ID, [admin], sender=authority) assert not managed_allow_list.administrators(admin) -def test_only_administrator_can_add_encryptor(managed_allow_list, admin, authority, encryptor): - managed_allow_list.setAdministratorCap(RITUAL_ID, admin, ENCRYPTOR_CAP, sender=authority) +def test_only_administrator_can_authorize(managed_allow_list, admin, authority, encryptor): + managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) with pytest.raises(Exception): - managed_allow_list.addEncryptor(RITUAL_ID, encryptor, sender=encryptor) - managed_allow_list.addEncryptor(RITUAL_ID, admin, sender=admin) - assert managed_allow_list.isEncryptor(admin) + managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=encryptor) + managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=admin) + assert managed_allow_list.authorizations(admin) -def test_only_administrator_can_remove_encryptor(managed_allow_list, admin, authority, encryptor): - managed_allow_list.setAdministratorCap(RITUAL_ID, admin, ENCRYPTOR_CAP, sender=authority) - managed_allow_list.addEncryptor(RITUAL_ID, encryptor, sender=admin) +def test_only_administrator_can_deauthorize(managed_allow_list, admin, authority, encryptor): + managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) with pytest.raises(Exception): - managed_allow_list.removeEncrypt(RITUAL_ID, encryptor, sender=encryptor) - assert not managed_allow_list.isEncryptor(encryptor) + managed_allow_list.deauthorize(RITUAL_ID, [encryptor], sender=encryptor) + managed_allow_list.deauthorize(RITUAL_ID, [encryptor], sender=admin) + assert not managed_allow_list.authorizations(admin) From 9dadabb992bbe4683f77f1f6f682b22a3809c5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Mon, 15 Apr 2024 22:13:03 +0200 Subject: [PATCH 006/105] Generalize modifier to set authorizations in GlobalAllowList --- contracts/contracts/coordination/GlobalAllowList.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/contracts/coordination/GlobalAllowList.sol b/contracts/contracts/coordination/GlobalAllowList.sol index dbfc866c1..7cf927434 100644 --- a/contracts/contracts/coordination/GlobalAllowList.sol +++ b/contracts/contracts/coordination/GlobalAllowList.sol @@ -27,7 +27,7 @@ contract GlobalAllowList is IEncryptionAuthorizer { coordinator = _coordinator; } - modifier onlyAuthority(uint32 ritualId) { + modifier canSetAuthorizations(uint32 ritualId) virtual { require( coordinator.getAuthority(ritualId) == msg.sender, "Only ritual authority is permitted" @@ -70,14 +70,14 @@ contract GlobalAllowList is IEncryptionAuthorizer { function authorize( uint32 ritualId, address[] calldata addresses - ) external onlyAuthority(ritualId) { + ) external canSetAuthorizations(ritualId) { setAuthorizations(ritualId, addresses, true); } function deauthorize( uint32 ritualId, address[] calldata addresses - ) external onlyAuthority(ritualId) { + ) external canSetAuthorizations(ritualId) { setAuthorizations(ritualId, addresses, false); } From 58445460c391369a2da6ae24c5f940a5f7d763ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Mon, 15 Apr 2024 22:23:20 +0200 Subject: [PATCH 007/105] ManagedAllowList now inherits from GlobalAllowList --- .../coordination/ManagedAllowList.sol | 71 +++---------------- 1 file changed, 8 insertions(+), 63 deletions(-) diff --git a/contracts/contracts/coordination/ManagedAllowList.sol b/contracts/contracts/coordination/ManagedAllowList.sol index 120b13615..49ac869cd 100644 --- a/contracts/contracts/coordination/ManagedAllowList.sol +++ b/contracts/contracts/coordination/ManagedAllowList.sol @@ -2,46 +2,26 @@ pragma solidity ^0.8.0; -import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "./IEncryptionAuthorizer.sol"; +import "./GlobalAllowList.sol"; import "./Coordinator.sol"; -contract ManagedAllowList is IEncryptionAuthorizer { - using MessageHashUtils for bytes32; - using ECDSA for bytes32; - - Coordinator public immutable coordinator; +contract ManagedAllowList is GlobalAllowList { mapping(bytes32 => uint256) public administrators; // TODO: Rename to allowances? - mapping(bytes32 => bool) public authorizations; event AdministratorCapSet(uint32 indexed ritualId, address indexed _address, uint256 cap); - event AddressAuthorizationSet( - uint32 indexed ritualId, - address indexed _address, - bool isAuthorized - ); - constructor(Coordinator _coordinator) { - require(address(_coordinator) != address(0), "Coordinator cannot be zero address"); - require(_coordinator.numberOfRituals() >= 0, "Invalid coordinator"); - coordinator = _coordinator; - } + constructor(Coordinator _coordinator) GlobalAllowList(_coordinator) {} - function lookupKey(uint32 ritualId, address encryptorOrAdmin) internal pure returns (bytes32) { - return keccak256(abi.encodePacked(ritualId, encryptorOrAdmin)); - } - - modifier onlyAuthority(uint32 ritualId) { + modifier onlyCohortAuthority(uint32 ritualId) { require( coordinator.getAuthority(ritualId) == msg.sender, - "Only ritual authority is permitted" + "Only cohort authority is permitted" ); _; } - modifier onlyAdministrator(uint32 ritualId) { + modifier canSetAuthorizations(uint32 ritualId) override { require( administrators[lookupKey(ritualId, msg.sender)] > 9, "Only administrator is permitted" @@ -65,50 +45,15 @@ contract ManagedAllowList is IEncryptionAuthorizer { uint32 ritualId, address[] calldata addresses, uint256 cap - ) external onlyAuthority(ritualId) { + ) external onlyCohortAuthority(ritualId) { setAdministratorCaps(ritualId, addresses, cap); } function removeAdministrators( uint32 ritualId, address[] calldata addresses - ) external onlyAuthority(ritualId) { + ) external onlyCohortAuthority(ritualId) { setAdministratorCaps(ritualId, addresses, 0); } - function isAddressAuthorized(uint32 ritualId, address encryptor) public view returns (bool) { - return authorizations[lookupKey(ritualId, encryptor)]; - } - - function isAuthorized( - uint32 ritualId, - bytes memory evidence, - bytes memory ciphertextHeader - ) external view override returns (bool) { - bytes32 digest = keccak256(ciphertextHeader); - address recoveredAddress = digest.toEthSignedMessageHash().recover(evidence); - return isAddressAuthorized(ritualId, recoveredAddress); - } - - function authorize( - uint32 ritualId, - address[] calldata addresses - ) external onlyAdministrator(ritualId) { - setAuthorizations(ritualId, addresses, true); - } - - function deauthorize( - uint32 ritualId, - address[] calldata addresses - ) external onlyAdministrator(ritualId) { - setAuthorizations(ritualId, addresses, false); - } - - function setAuthorizations(uint32 ritualId, address[] calldata addresses, bool value) internal { - require(coordinator.isRitualActive(ritualId), "Only active rituals can set authorizations"); - for (uint256 i = 0; i < addresses.length; i++) { - authorizations[lookupKey(ritualId, addresses[i])] = value; - emit AddressAuthorizationSet(ritualId, addresses[i], value); - } - } } From 4f95871b62ffcfe1428d0286e6b5f3622bb6d911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 16 Apr 2024 11:34:24 +0200 Subject: [PATCH 008/105] Rename allowances in ManagedAllowList --- contracts/contracts/coordination/ManagedAllowList.sol | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/contracts/contracts/coordination/ManagedAllowList.sol b/contracts/contracts/coordination/ManagedAllowList.sol index 49ac869cd..c8a2245a7 100644 --- a/contracts/contracts/coordination/ManagedAllowList.sol +++ b/contracts/contracts/coordination/ManagedAllowList.sol @@ -7,7 +7,7 @@ import "./Coordinator.sol"; contract ManagedAllowList is GlobalAllowList { - mapping(bytes32 => uint256) public administrators; // TODO: Rename to allowances? + mapping(bytes32 => uint256) internal allowance; event AdministratorCapSet(uint32 indexed ritualId, address indexed _address, uint256 cap); @@ -23,12 +23,16 @@ contract ManagedAllowList is GlobalAllowList { modifier canSetAuthorizations(uint32 ritualId) override { require( - administrators[lookupKey(ritualId, msg.sender)] > 9, + getAllowance(ritualId, msg.sender) > 0, "Only administrator is permitted" ); _; } + function getAllowance(uint32 ritualId, address admin) public view returns(uint256) { + return allowance[lookupKey(ritualId, admin)]; + } + function setAdministratorCaps( uint32 ritualId, address[] calldata addresses, @@ -36,7 +40,7 @@ contract ManagedAllowList is GlobalAllowList { ) internal { require(coordinator.isRitualActive(ritualId), "Only active rituals can set administrator caps"); for (uint256 i = 0; i < addresses.length; i++) { - administrators[lookupKey(ritualId, addresses[i])] = value; + allowance[lookupKey(ritualId, addresses[i])] = value; emit AdministratorCapSet(ritualId, addresses[i], value); } } From c97636b4b1967b7e6e1fddd3d8210500d8deafb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 16 Apr 2024 11:35:46 +0200 Subject: [PATCH 009/105] Fix tests for ManagedAllowList --- contracts/test/CoordinatorTestSet.sol | 19 --- .../test/EncryptionAuthorizerTestSet.sol | 21 +++ tests/test_managed_allow_list.py | 121 ++++++------------ 3 files changed, 58 insertions(+), 103 deletions(-) create mode 100644 contracts/test/EncryptionAuthorizerTestSet.sol diff --git a/contracts/test/CoordinatorTestSet.sol b/contracts/test/CoordinatorTestSet.sol index 4d15b480e..d7664553f 100644 --- a/contracts/test/CoordinatorTestSet.sol +++ b/contracts/test/CoordinatorTestSet.sol @@ -33,22 +33,3 @@ contract ChildApplicationForCoordinatorMock is ITACoChildApplication { // solhint-disable-next-line no-empty-blocks function penalize(address _stakingProvider) external {} } - -// /** -// * @notice Intermediary contract for testing operator -// */ -// contract Intermediary { -// TACoApplication public immutable application; - -// constructor(TACoApplication _application) { -// application = _application; -// } - -// function bondOperator(address _operator) external { -// application.bondOperator(address(this), _operator); -// } - -// function confirmOperatorAddress() external { -// application.confirmOperatorAddress(); -// } -// } diff --git a/contracts/test/EncryptionAuthorizerTestSet.sol b/contracts/test/EncryptionAuthorizerTestSet.sol new file mode 100644 index 000000000..86b074f4c --- /dev/null +++ b/contracts/test/EncryptionAuthorizerTestSet.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.0; + + +contract CoordinatorForEncryptionAuthorizerMock { + + uint32 public numberOfRituals; + mapping(uint32 => address) public getAuthority; + mapping(uint32 => bool) public isRitualActive; + + function mockNewRitual(address authority) external { + getAuthority[numberOfRituals] = authority; + isRitualActive[numberOfRituals] = true; + numberOfRituals += 1; + } + + function mockEndRitual(uint32 ritualId) external { + isRitualActive[ritualId] = false; + } +} diff --git a/tests/test_managed_allow_list.py b/tests/test_managed_allow_list.py index 376860c9e..422eeca0d 100644 --- a/tests/test_managed_allow_list.py +++ b/tests/test_managed_allow_list.py @@ -1,158 +1,111 @@ import pytest -# Stuff copied from test_coordinator.py -TIMEOUT = 1000 -MAX_DKG_SIZE = 31 -FEE_RATE = 42 -ERC20_SUPPLY = 10**24 +RITUAL_ID = 0 +ADMIN_CAP = 5 @pytest.fixture(scope="module") -def nodes(accounts): - return sorted(accounts[:MAX_DKG_SIZE], key=lambda x: x.address.lower()) +def deployer(accounts): + return accounts[0] @pytest.fixture(scope="module") -def initiator(accounts): - initiator_index = MAX_DKG_SIZE + 1 - assert len(accounts) >= initiator_index - return accounts[initiator_index] +def authority(accounts): + return accounts[1] @pytest.fixture(scope="module") -def deployer(accounts): - deployer_index = MAX_DKG_SIZE + 2 - assert len(accounts) >= deployer_index - return accounts[deployer_index] +def admin(accounts): + return accounts[2] @pytest.fixture(scope="module") -def treasury(accounts): - treasury_index = MAX_DKG_SIZE + 3 - assert len(accounts) >= treasury_index - return accounts[treasury_index] - - -@pytest.fixture() -def application(project, deployer, nodes): - contract = project.ChildApplicationForCoordinatorMock.deploy(sender=deployer) - for n in nodes: - contract.updateOperator(n, n, sender=deployer) - contract.updateAuthorization(n, 42, sender=deployer) - return contract - - -@pytest.fixture() -def erc20(project, initiator): - token = project.TestToken.deploy(ERC20_SUPPLY, sender=initiator) - return token +def encryptor(accounts): + return accounts[3] @pytest.fixture() -def coordinator(project, deployer, application, erc20, initiator, oz_dependency): - contract = project.Coordinator.deploy( - application.address, - erc20.address, - FEE_RATE, +def coordinator(project, deployer): + contract = project.CoordinatorForEncryptionAuthorizerMock.deploy( sender=deployer, ) - - encoded_initializer_function = contract.initialize.encode_input(TIMEOUT, MAX_DKG_SIZE, deployer) - proxy = oz_dependency.TransparentUpgradeableProxy.deploy( - contract.address, - deployer, - encoded_initializer_function, - sender=deployer, - ) - proxy_contract = project.Coordinator.at(proxy.address) - - proxy_contract.grantRole(contract.INITIATOR_ROLE(), initiator, sender=deployer) - return proxy_contract - - -# My fixtures - -RITUAL_ID = 1 -ADMIN_CAP = 5 - - -@pytest.fixture() -def authority(accounts): - return accounts[1] + return contract @pytest.fixture() -def admin(accounts): - return accounts[2] +def brand_new_managed_allow_list(project, coordinator, authority): + return project.ManagedAllowList.deploy(coordinator.address, sender=authority) @pytest.fixture() -def encryptor(accounts): - return accounts[3] +def managed_allow_list(brand_new_managed_allow_list, coordinator, deployer, authority): + coordinator.mockNewRitual(authority, sender=deployer) + return brand_new_managed_allow_list -@pytest.fixture() -def managed_allow_list(project, coordinator, authority): - return project.ManagedAllowList.deploy(coordinator.address, sender=authority) - +def test_initial_parameters(brand_new_managed_allow_list, coordinator, admin, encryptor): + assert brand_new_managed_allow_list.coordinator() == coordinator.address + assert not brand_new_managed_allow_list.getAllowance(RITUAL_ID, admin.address) + assert not brand_new_managed_allow_list.isAddressAuthorized(RITUAL_ID, encryptor.address) -def test_initial_parameters(managed_allow_list, coordinator, authority, admin, encryptor): - assert managed_allow_list.coordinator() == coordinator.address - assert not managed_allow_list.administrators(admin.address) - assert not managed_allow_list.authorizations(encryptor.address) +# TODO: Missing checks for events below def test_add_administrator(managed_allow_list, authority, admin): managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) - assert managed_allow_list.administrators(admin) == ADMIN_CAP + assert managed_allow_list.getAllowance(RITUAL_ID, admin) == ADMIN_CAP def test_remove_administrator(managed_allow_list, authority, admin): managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) managed_allow_list.removeAdministrators(RITUAL_ID, [admin], sender=authority) - assert not managed_allow_list.administrators(admin) + assert not managed_allow_list.getAllowance(RITUAL_ID, admin) def test_authorize(managed_allow_list, authority, admin, encryptor): managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=admin) - assert managed_allow_list.authorizations(encryptor) + assert managed_allow_list.isAddressAuthorized(RITUAL_ID, encryptor) def test_deauthorize(managed_allow_list, admin, authority, encryptor): managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=admin) managed_allow_list.deauthorize(RITUAL_ID, [encryptor], sender=admin) - assert not managed_allow_list.authorizations(encryptor) + assert not managed_allow_list.isAddressAuthorized(RITUAL_ID, encryptor) def test_only_authority_can_add_administrator(managed_allow_list, admin, authority, encryptor): with pytest.raises(Exception): managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=admin) - managed_allow_list.addAdministrators(RITUAL_ID, [admin], sender=authority) - assert managed_allow_list.administrators(admin) + managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) + assert managed_allow_list.getAllowance(RITUAL_ID, admin) def test_only_authority_can_remove_administrator(managed_allow_list, admin, authority): - managed_allow_list.addAdministrator(RITUAL_ID, admin, sender=authority) + managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) with pytest.raises(Exception): managed_allow_list.removeAdministrators(RITUAL_ID, [admin], sender=admin) managed_allow_list.removeAdministrators(RITUAL_ID, [admin], sender=authority) - assert not managed_allow_list.administrators(admin) + assert not managed_allow_list.getAllowance(RITUAL_ID, admin) def test_only_administrator_can_authorize(managed_allow_list, admin, authority, encryptor): managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) with pytest.raises(Exception): managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=encryptor) + with pytest.raises(Exception): + managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=authority) managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=admin) - assert managed_allow_list.authorizations(admin) + assert managed_allow_list.isAddressAuthorized(RITUAL_ID, encryptor) def test_only_administrator_can_deauthorize(managed_allow_list, admin, authority, encryptor): managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) with pytest.raises(Exception): managed_allow_list.deauthorize(RITUAL_ID, [encryptor], sender=encryptor) + with pytest.raises(Exception): + managed_allow_list.deauthorize(RITUAL_ID, [encryptor], sender=authority) managed_allow_list.deauthorize(RITUAL_ID, [encryptor], sender=admin) - assert not managed_allow_list.authorizations(admin) + assert not managed_allow_list.isAddressAuthorized(RITUAL_ID, encryptor) From 182ec4d2e8b6b76c3d000421e41c56a921929e75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 16 Apr 2024 12:58:21 +0200 Subject: [PATCH 010/105] Draft for Subscriptions and how can be used with AllowLists --- .../coordination/GlobalAllowList.sol | 4 + .../coordination/ManagedAllowList.sol | 11 +++ .../contracts/coordination/Subscription.sol | 90 +++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 contracts/contracts/coordination/Subscription.sol diff --git a/contracts/contracts/coordination/GlobalAllowList.sol b/contracts/contracts/coordination/GlobalAllowList.sol index 7cf927434..609a30be5 100644 --- a/contracts/contracts/coordination/GlobalAllowList.sol +++ b/contracts/contracts/coordination/GlobalAllowList.sol @@ -15,6 +15,8 @@ contract GlobalAllowList is IEncryptionAuthorizer { mapping(bytes32 => bool) internal authorizations; + mapping(uint32 => uint256) public authActions; + event AddressAuthorizationSet( uint32 indexed ritualId, address indexed _address, @@ -90,5 +92,7 @@ contract GlobalAllowList is IEncryptionAuthorizer { authorizations[lookupKey(ritualId, addresses[i])] = value; emit AddressAuthorizationSet(ritualId, addresses[i], value); } + + authActions[ritualId] += addresses.length; } } diff --git a/contracts/contracts/coordination/ManagedAllowList.sol b/contracts/contracts/coordination/ManagedAllowList.sol index c8a2245a7..f01021ca1 100644 --- a/contracts/contracts/coordination/ManagedAllowList.sol +++ b/contracts/contracts/coordination/ManagedAllowList.sol @@ -33,6 +33,16 @@ contract ManagedAllowList is GlobalAllowList { return allowance[lookupKey(ritualId, admin)]; } + function _beforeSetAuthorization( + uint32 ritualId, + address[] calldata addresses, + bool value + ) internal view override { + // FIXME: How to match ritual ID with subscription ID? + require(authActions[ritualId] < subscription.authorizationActionsCap(ritualId)); + } + + function setAdministratorCaps( uint32 ritualId, address[] calldata addresses, @@ -43,6 +53,7 @@ contract ManagedAllowList is GlobalAllowList { allowance[lookupKey(ritualId, addresses[i])] = value; emit AdministratorCapSet(ritualId, addresses[i], value); } + authActions[ritualId] += addresses.length; } function addAdministrators( diff --git a/contracts/contracts/coordination/Subscription.sol b/contracts/contracts/coordination/Subscription.sol new file mode 100644 index 000000000..2963a014f --- /dev/null +++ b/contracts/contracts/coordination/Subscription.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "./Coordinator.sol"; + +abstract contract Subscription { + + struct SubscriptionInfo { + uint256 paidFor; + uint256 spent; + uint256 expiration; + address subscriber; + } + + Coordinator coordinator; + IERC20 feeToken; + + mapping(uint32 => SubscriptionInfo) public subscriptions; + uint32 public numberOfSubscriptions; + + constructor(Coordinator _coordinator, IERC20 _feeToken){ + // TODO: coordintaor and token checks + coordinator = _coordinator; + feeToken = _feeToken; + } + + function subscriptionFee() pure public returns(uint256) { + return 42 * 10**20; // TODO + } + + function baseExpiration() pure public returns(uint256) { + return 52 weeks; // TODO + } + + function newSubscription() external returns(uint256){ + + uint32 subscriptionId = numberOfSubscriptions; + SubscriptionInfo storage sub = subscriptions[subscriptionId]; + sub.subscriber = msg.sender; + paySubscriptionFor(subscriptionId); + + numberOfSubscriptions += 1; + } + + function paySubscriptionFor(uint32 subscriptionId) public virtual { + uint256 amount = subscriptionFee(); + + SubscriptionInfo storage sub = subscriptions[subscriptionId]; + sub.paidFor += amount; + sub.expiration += baseExpiration(); + + feeToken.safeTransferFrom(msg.sender, address(this), amount); + } + + function canSpendFromSubscription( + uint32 subscriptionId, + address spender + ) public returns(bool){ + // By default, only coordinator can spend from subscription + return spender == address(coordinator); + } + + function spendFromSubscription(uint32 subscriptionId, uint256 amount) external { + require(canSpendFromSubscription(subscriptionId, msg.sender)); + feeToken.safeTransferFrom(address(this), msg.sender, amount); + } + + // TODO: Withdraw methods for DAO Treasury, cancel subscription, etc +} + +// An upfront subscription for a cohort with a predefined duration and a max number of encryptors +contract UpfrontSubscriptionWithEncryptorsCap is Subscription { + + uint256 constant DEFAULT_CAP = 1000; + + mapping(uint32 => uint256) authorizationActionCaps; + + constructor(Coordinator _coordinator, IERC20 _feeToken) + Subscription(_coordinator, _feeToken){ + + } + + function paySubscriptionFor(uint32 subscriptionId) public virtual override { + super.paySubscriptionFor(subscriptionId); + authorizationActionCaps[subscriptionId] += DEFAULT_CAP; + } +} From 772b502ec511314ba592fe56ae92da2622f598cb Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Mon, 15 Apr 2024 20:44:27 +0200 Subject: [PATCH 011/105] wip: change of plans was a mistakerino --- contracts/contracts/coordination/GlobalAllowList.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/contracts/coordination/GlobalAllowList.sol b/contracts/contracts/coordination/GlobalAllowList.sol index 609a30be5..e98198a73 100644 --- a/contracts/contracts/coordination/GlobalAllowList.sol +++ b/contracts/contracts/coordination/GlobalAllowList.sol @@ -87,7 +87,6 @@ contract GlobalAllowList is IEncryptionAuthorizer { require(coordinator.isRitualActive(ritualId), "Only active rituals can set authorizations"); _beforeSetAuthorization(ritualId, addresses, value); - for (uint256 i = 0; i < addresses.length; i++) { authorizations[lookupKey(ritualId, addresses[i])] = value; emit AddressAuthorizationSet(ritualId, addresses[i], value); From 4b828379cd6f0a8ce64034b8af8752a0755fa22c Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Tue, 16 Apr 2024 11:37:33 +0200 Subject: [PATCH 012/105] wip: subscription manager draft --- .../coordination/ManagedAllowList.sol | 8 +- .../coordination/SubscriptionManager.sol | 118 ++++++++++++++++++ 2 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 contracts/contracts/coordination/SubscriptionManager.sol diff --git a/contracts/contracts/coordination/ManagedAllowList.sol b/contracts/contracts/coordination/ManagedAllowList.sol index f01021ca1..0927e7068 100644 --- a/contracts/contracts/coordination/ManagedAllowList.sol +++ b/contracts/contracts/coordination/ManagedAllowList.sol @@ -6,7 +6,6 @@ import "./GlobalAllowList.sol"; import "./Coordinator.sol"; contract ManagedAllowList is GlobalAllowList { - mapping(bytes32 => uint256) internal allowance; event AdministratorCapSet(uint32 indexed ritualId, address indexed _address, uint256 cap); @@ -42,13 +41,15 @@ contract ManagedAllowList is GlobalAllowList { require(authActions[ritualId] < subscription.authorizationActionsCap(ritualId)); } - function setAdministratorCaps( uint32 ritualId, address[] calldata addresses, uint256 value ) internal { - require(coordinator.isRitualActive(ritualId), "Only active rituals can set administrator caps"); + require( + coordinator.isRitualActive(ritualId), + "Only active rituals can set administrator caps" + ); for (uint256 i = 0; i < addresses.length; i++) { allowance[lookupKey(ritualId, addresses[i])] = value; emit AdministratorCapSet(ritualId, addresses[i], value); @@ -70,5 +71,4 @@ contract ManagedAllowList is GlobalAllowList { ) external onlyCohortAuthority(ritualId) { setAdministratorCaps(ritualId, addresses, 0); } - } diff --git a/contracts/contracts/coordination/SubscriptionManager.sol b/contracts/contracts/coordination/SubscriptionManager.sol new file mode 100644 index 000000000..d1c43c9ec --- /dev/null +++ b/contracts/contracts/coordination/SubscriptionManager.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.0; + +import "./ManagedAllowList.sol"; + +contract SubscriptionManager is ManagedAllowList { + struct Subscription { + address subscriber; + uint256 amount; + uint256 period; // Represents the end timestamp of the subscription + } + + mapping(bytes32 => Subscription) public subscriptions; + + event SubscriptionCreated( + uint32 indexed ritualId, + address indexed subscriber, + uint256 amount, + uint256 period + ); + event SubscriptionTerminated(uint32 indexed ritualId, address indexed subscriber); + event SubscriptionRenewed( + uint32 indexed ritualId, + address indexed subscriber, + uint256 amount, + uint256 period + ); + + function createSubscription( + uint32 ritualId, + address _subscriber, + uint256 _amount, + uint256 _period + ) external onlyCohortAuthority(ritualId) { + bytes32 key = lookupKey(ritualId, _subscriber); + require( + subscriptions[key].subscriber == address(0), + "Subscriber already has an active subscription" + ); + + Subscription memory newSubscription = Subscription({ + subscriber: _subscriber, + amount: _amount, + period: _period + }); + + subscriptions[key] = newSubscription; + + emit SubscriptionCreated(ritualId, _subscriber, _amount, _period); + } + + function terminateSubscription( + uint32 ritualId, + address _subscriber + ) external onlySubscriber(ritualId, _subscriber) { + bytes32 key = lookupKey(ritualId, _subscriber); + require( + subscriptions[key].subscriber != address(0), + "No active subscription found for the subscriber" + ); + + delete subscriptions[key]; + + emit SubscriptionTerminated(ritualId, _subscriber); + + // TODO: Return the _amount to the subscriber. Is it a native asset or ERC20? + } + + // TODO: Handle a native asset or ERC20 as _amount + function renewSubscription( + uint32 ritualId, + address _subscriber, + uint256 _amount, + uint256 _period + ) external onlyCohortAuthority(ritualId) { + bytes32 key = lookupKey(ritualId, _subscriber); + require( + subscriptions[key].subscriber != address(0), + "No active subscription found for the subscriber" + ); + + Subscription storage subscription = subscriptions[key]; + subscription.amount = _amount; + subscription.period = _period; + + emit SubscriptionRenewed(ritualId, _subscriber, _amount, _period); + } + + function _beforeIsAuthorized( + uint32 ritualId, + bytes memory evidence, + bytes memory ciphertextHeader + ) internal view override { + bytes32 digest = keccak256(ciphertextHeader); + address recoveredAddress = digest.toEthSignedMessageHash().recover(evidence); + Subscription memory subscription = subscriptions[lookupKey(ritualId, recoveredAddress)]; + require( + subscription.subscriber != address(0) && subscription.period >= block.timestamp, + "Subscriber is not authorized or subscription has expired" + ); + } + + // TODO: What do I do with the `bool value`? + function _beforeSetAuthorization( + uint32 ritualId, + address[] calldata addresses, + bool value + ) internal override { + for (uint256 i = 0; i < addresses.length; i++) { + Subscription memory subscription = subscriptions[lookupKey(ritualId, addresses[i])]; + require( + subscription.subscriber != address(0) && subscription.period >= block.timestamp, + "Subscriber is not authorized or subscription has expired" + ); + } + } +} From 76e57daed86680b7e773aede70b16e25ba85e49e Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Thu, 18 Apr 2024 13:24:38 +0200 Subject: [PATCH 013/105] wip: add way too much comments --- .../coordination/GlobalAllowList.sol | 69 ++++++++ .../coordination/ManagedAllowList.sol | 89 +++++++++- .../contracts/coordination/Subscription.sol | 153 ++++++++++++++++-- .../coordination/SubscriptionManager.sol | 118 -------------- 4 files changed, 287 insertions(+), 142 deletions(-) delete mode 100644 contracts/contracts/coordination/SubscriptionManager.sol diff --git a/contracts/contracts/coordination/GlobalAllowList.sol b/contracts/contracts/coordination/GlobalAllowList.sol index e98198a73..db0e96c7e 100644 --- a/contracts/contracts/coordination/GlobalAllowList.sol +++ b/contracts/contracts/coordination/GlobalAllowList.sol @@ -7,6 +7,10 @@ import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "./IEncryptionAuthorizer.sol"; import "./Coordinator.sol"; +/** + * @title GlobalAllowList + * @notice Manages a global allow list of addresses that are authorized to decrypt ciphertexts. + */ contract GlobalAllowList is IEncryptionAuthorizer { using MessageHashUtils for bytes32; using ECDSA for bytes32; @@ -17,18 +21,33 @@ contract GlobalAllowList is IEncryptionAuthorizer { mapping(uint32 => uint256) public authActions; + /** + * @notice Emitted when an address authorization is set + * @param ritualId The ID of the ritual + * @param _address The address that is authorized + * @param isAuthorized The authorization status + */ event AddressAuthorizationSet( uint32 indexed ritualId, address indexed _address, bool isAuthorized ); + /** + * @notice Sets the coordinator contract + * @dev The coordinator contract cannot be a zero address and must have a valid number of rituals + * @param _coordinator The address of the coordinator contract + */ constructor(Coordinator _coordinator) { require(address(_coordinator) != address(0), "Coordinator cannot be zero address"); require(_coordinator.numberOfRituals() >= 0, "Invalid coordinator"); coordinator = _coordinator; } + /** + * @notice Checks if the sender is the authority of the ritual + * @param ritualId The ID of the ritual + */ modifier canSetAuthorizations(uint32 ritualId) virtual { require( coordinator.getAuthority(ritualId) == msg.sender, @@ -37,20 +56,46 @@ contract GlobalAllowList is IEncryptionAuthorizer { _; } + /** + * @notice Returns the key used to lookup authorizations + * @param ritualId The ID of the ritual + * @param encryptor The address of the encryptor + * @return The key used to lookup authorizations + */ function lookupKey(uint32 ritualId, address encryptor) internal pure returns (bytes32) { return keccak256(abi.encodePacked(ritualId, encryptor)); } + /** + * @notice Checks if an address is authorized for a ritual + * @param ritualId The ID of the ritual + * @param encryptor The address of the encryptor + * @return The authorization status + */ function isAddressAuthorized(uint32 ritualId, address encryptor) public view returns (bool) { return authorizations[lookupKey(ritualId, encryptor)]; } + /** + * @notice Checks if an address is authorized for a ritual + * @dev This function is called before the isAuthorized function + * @param ritualId The ID of the ritual + * @param evidence The evidence provided + * @param ciphertextHeader The header of the ciphertext + */ function _beforeIsAuthorized( uint32 ritualId, bytes memory evidence, bytes memory ciphertextHeader ) internal view virtual {} + /** + * @notice Checks if an address is authorized for a ritual + * @param ritualId The ID of the ritual + * @param evidence The evidence provided + * @param ciphertextHeader The header of the ciphertext + * @return The authorization status + */ function isAuthorized( uint32 ritualId, bytes memory evidence, @@ -63,12 +108,24 @@ contract GlobalAllowList is IEncryptionAuthorizer { return isAddressAuthorized(ritualId, recoveredAddress); } + /** + * @notice Checks if an address is authorized for a ritual + * @dev This function is called before the setAuthorizations function + * @param ritualId The ID of the ritual + * @param addresses The addresses to be authorized + * @param value The authorization status + */ function _beforeSetAuthorization( uint32 ritualId, address[] calldata addresses, bool value ) internal view virtual {} + /** + * @notice Authorizes a list of addresses for a ritual + * @param ritualId The ID of the ritual + * @param addresses The addresses to be authorized + */ function authorize( uint32 ritualId, address[] calldata addresses @@ -76,6 +133,11 @@ contract GlobalAllowList is IEncryptionAuthorizer { setAuthorizations(ritualId, addresses, true); } + /** + * @notice Deauthorizes a list of addresses for a ritual + * @param ritualId The ID of the ritual + * @param addresses The addresses to be deauthorized + */ function deauthorize( uint32 ritualId, address[] calldata addresses @@ -83,6 +145,13 @@ contract GlobalAllowList is IEncryptionAuthorizer { setAuthorizations(ritualId, addresses, false); } + /** + * @notice Sets the authorization status for a list of addresses for a ritual + * @dev Only active rituals can set authorizations + * @param ritualId The ID of the ritual + * @param addresses The addresses to be authorized or deauthorized + * @param value The authorization status + */ function setAuthorizations(uint32 ritualId, address[] calldata addresses, bool value) internal { require(coordinator.isRitualActive(ritualId), "Only active rituals can set authorizations"); diff --git a/contracts/contracts/coordination/ManagedAllowList.sol b/contracts/contracts/coordination/ManagedAllowList.sol index 0927e7068..2b405f585 100644 --- a/contracts/contracts/coordination/ManagedAllowList.sol +++ b/contracts/contracts/coordination/ManagedAllowList.sol @@ -4,14 +4,51 @@ pragma solidity ^0.8.0; import "./GlobalAllowList.sol"; import "./Coordinator.sol"; +import {UpfrontSubscriptionWithEncryptorsCap} from "./Subscription.sol"; +/** + * @title ManagedAllowList + * @notice Manages a list of addresses that are authorized to decrypt ciphertexts, with additional management features. + * This contract extends the GlobalAllowList contract and introduces additional management features. + * It maintains a reference to a Subscription contract, which is used to manage the authorization caps for different addresses and rituals. + * The Subscription contract is used to enforce limits on the number of authorization actions that can be performed, and these limits can be set and updated through the ManagedAllowList contract. + */ contract ManagedAllowList is GlobalAllowList { mapping(bytes32 => uint256) internal allowance; + /** + * @notice The Subscription contract used to manage authorization caps + */ + UpfrontSubscriptionWithEncryptorsCap public subscription; + + /** + * @notice Emitted when an administrator cap is set + * @param ritualId The ID of the ritual + * @param _address The address of the administrator + * @param cap The cap value + */ event AdministratorCapSet(uint32 indexed ritualId, address indexed _address, uint256 cap); - constructor(Coordinator _coordinator) GlobalAllowList(_coordinator) {} + /** + * @notice Sets the coordinator and subscription contracts + * @dev The coordinator and subscription contracts cannot be zero addresses + * @param _coordinator The address of the coordinator contract + * @param _subscription The address of the subscription contract + */ + constructor( + Coordinator _coordinator, + UpfrontSubscriptionWithEncryptorsCap _subscription + ) GlobalAllowList(_coordinator) { + require(address(_coordinator) != address(0), "Coordinator cannot be the zero address"); + require(address(_subscription) != address(0), "Subscription cannot be the zero address"); + subscription = _subscription; + } + /** + * @notice Checks if the sender is the authority of the ritual + * @dev This function overrides the canSetAuthorizations modifier in the GlobalAllowList contract + * @param ritualId The ID of the ritual + */ modifier onlyCohortAuthority(uint32 ritualId) { require( coordinator.getAuthority(ritualId) == msg.sender, @@ -20,27 +57,52 @@ contract ManagedAllowList is GlobalAllowList { _; } + /** + * @notice Checks if the sender has allowance to set authorizations + * @dev This function overrides the canSetAuthorizations modifier in the GlobalAllowList contract + * @param ritualId The ID of the ritual + */ modifier canSetAuthorizations(uint32 ritualId) override { - require( - getAllowance(ritualId, msg.sender) > 0, - "Only administrator is permitted" - ); + require(getAllowance(ritualId, msg.sender) > 0, "Only administrator is permitted"); _; } - function getAllowance(uint32 ritualId, address admin) public view returns(uint256) { + /** + * @notice Returns the allowance of an administrator for a ritual + * @param ritualId The ID of the ritual + * @param admin The address of the administrator + * @return The allowance of the administrator + */ + function getAllowance(uint32 ritualId, address admin) public view returns (uint256) { return allowance[lookupKey(ritualId, admin)]; } + /** + * @notice Checks if an address is authorized for a ritual + * @dev This function is called before the setAuthorizations function + * @param ritualId The ID of the ritual + * @param addresses The addresses to be authorized + * @param value The authorization status + */ function _beforeSetAuthorization( uint32 ritualId, address[] calldata addresses, bool value ) internal view override { - // FIXME: How to match ritual ID with subscription ID? - require(authActions[ritualId] < subscription.authorizationActionsCap(ritualId)); + for (uint256 i = 0; i < addresses.length; i++) { + require( + authActions[ritualId] < subscription.authorizationActionsCap(ritualId, addresses[i]) + ); + } } + /** + * @notice Sets the administrator caps for a ritual + * @dev Only active rituals can set administrator caps + * @param ritualId The ID of the ritual + * @param addresses The addresses of the administrators + * @param value The cap value + */ function setAdministratorCaps( uint32 ritualId, address[] calldata addresses, @@ -57,6 +119,12 @@ contract ManagedAllowList is GlobalAllowList { authActions[ritualId] += addresses.length; } + /** + * @notice Adds administrators for a ritual + * @param ritualId The ID of the ritual + * @param addresses The addresses of the administrators + * @param cap The cap value + */ function addAdministrators( uint32 ritualId, address[] calldata addresses, @@ -65,6 +133,11 @@ contract ManagedAllowList is GlobalAllowList { setAdministratorCaps(ritualId, addresses, cap); } + /** + * @notice Removes administrators for a ritual + * @param ritualId The ID of the ritual + * @param addresses The addresses of the administrators + */ function removeAdministrators( uint32 ritualId, address[] calldata addresses diff --git a/contracts/contracts/coordination/Subscription.sol b/contracts/contracts/coordination/Subscription.sol index 2963a014f..03b53c309 100644 --- a/contracts/contracts/coordination/Subscription.sol +++ b/contracts/contracts/coordination/Subscription.sol @@ -6,8 +6,16 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "./Coordinator.sol"; +using SafeERC20 for IERC20; + +/** + * @title Subscription + * @notice Manages the subscription information for rituals. + * @dev This contract is abstract and should be extended by a concrete implementation. + * It maintains a reference to a Coordinator contract and a fee token (ERC20). + * Each subscription has an associated SubscriptionInfo struct which keeps track of the subscription details. + */ abstract contract Subscription { - struct SubscriptionInfo { uint256 paidFor; uint256 spent; @@ -18,33 +26,80 @@ abstract contract Subscription { Coordinator coordinator; IERC20 feeToken; + // Mapping from subscription ID to subscription info mapping(uint32 => SubscriptionInfo) public subscriptions; + + // Mapping from (ritualId, address) to subscription ID + mapping(bytes32 => uint32) public subscribers; + uint32 public numberOfSubscriptions; - constructor(Coordinator _coordinator, IERC20 _feeToken){ - // TODO: coordintaor and token checks + // TODO: DAO Treasury + address public beneficiary; + + /** + * @notice Sets the coordinator and fee token contracts + * @dev The coordinator and fee token contracts cannot be zero addresses + * @param _coordinator The address of the coordinator contract + * @param _feeToken The address of the fee token contract + * @param _beneficiary The address of the beneficiary + */ + constructor(Coordinator _coordinator, IERC20 _feeToken, address _beneficiary) { + require(address(_coordinator) != address(0), "Coordinator cannot be the zero address"); + require(address(_feeToken) != address(0), "Fee token cannot be the zero address"); + require(_beneficiary != address(0), "Beneficiary cannot be the zero address"); coordinator = _coordinator; feeToken = _feeToken; + beneficiary = _beneficiary; } - function subscriptionFee() pure public returns(uint256) { - return 42 * 10**20; // TODO + /** + * @notice Returns the subscription fee + * @return The subscription fee + */ + function subscriptionFee() public pure returns (uint256) { + return 42 * 10 ** 20; // TODO } - function baseExpiration() pure public returns(uint256) { - return 52 weeks; // TODO + /** + * @notice Returns the base expiration duration + * @return The base expiration duration + */ + function baseExpiration() public pure returns (uint256) { + return 52 weeks; // TODO } - function newSubscription() external returns(uint256){ + /** + * @notice Returns the key used to lookup authorizations + * @param ritualId The ID of the ritual + * @param encryptor The address of the encryptor + * @return The key used to lookup authorizations + */ + function lookupKey(uint32 ritualId, address encryptor) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(ritualId, encryptor)); + } + /** + * @notice Creates a new subscription + * @param ritualId The ID of the ritual + * @return The ID of the new subscription + */ + function newSubscription(uint32 ritualId) external returns (uint256) { uint32 subscriptionId = numberOfSubscriptions; SubscriptionInfo storage sub = subscriptions[subscriptionId]; sub.subscriber = msg.sender; paySubscriptionFor(subscriptionId); + subscribers[lookupKey(ritualId, msg.sender)] = subscriptionId; + numberOfSubscriptions += 1; + return subscriptionId; } + /** + * @notice Pays for a subscription + * @param subscriptionId The ID of the subscription + */ function paySubscriptionFor(uint32 subscriptionId) public virtual { uint256 amount = subscriptionFee(); @@ -55,36 +110,102 @@ abstract contract Subscription { feeToken.safeTransferFrom(msg.sender, address(this), amount); } + /** + * @notice Checks if a spender can spend from a subscription + * @param subscriptionId The ID of the subscription + * @param spender The address of the spender + * @return True if the spender can spend from the subscription, false otherwise + */ function canSpendFromSubscription( uint32 subscriptionId, address spender - ) public returns(bool){ + ) public returns (bool) { // By default, only coordinator can spend from subscription return spender == address(coordinator); } + /** + * @notice Spends from a subscription + * @param subscriptionId The ID of the subscription + * @param amount The amount to spend + */ function spendFromSubscription(uint32 subscriptionId, uint256 amount) external { require(canSpendFromSubscription(subscriptionId, msg.sender)); feeToken.safeTransferFrom(address(this), msg.sender, amount); } // TODO: Withdraw methods for DAO Treasury, cancel subscription, etc + + /** + * @notice Withdraws the contract balance to the beneficiary + * @param amount The amount to withdraw + */ + function withdrawToBeneficiary(uint256 amount) external { + require(msg.sender == beneficiary, "Only the beneficiary can withdraw"); + uint256 contractBalance = feeToken.balanceOf(address(this)); + require(contractBalance >= amount, "Insufficient contract balance"); + feeToken.safeTransfer(beneficiary, amount); + } + + /** + * @notice Cancels a subscription + * @param ritualId The ID of the ritual + * @param subscriptionId The ID of the subscription + */ + function cancelSubscription(uint32 ritualId, uint32 subscriptionId) external { + require( + msg.sender == subscriptions[subscriptionId].subscriber, + "Only the subscriber can cancel the subscription" + ); + uint256 refundAmount = subscriptions[subscriptionId].paidFor; + feeToken.safeTransfer(msg.sender, refundAmount); + delete subscriptions[subscriptionId]; + delete subscribers[lookupKey(ritualId, msg.sender)]; + } } -// An upfront subscription for a cohort with a predefined duration and a max number of encryptors +/** + * @title UpfrontSubscriptionWithEncryptorsCap + * @notice Manages upfront subscriptions with a cap on the number of encryptors. + * @dev This contract extends the Subscription contract and introduces a cap on the number of encryptors. + */ contract UpfrontSubscriptionWithEncryptorsCap is Subscription { - uint256 constant DEFAULT_CAP = 1000; + // Mapping from subscription ID to the number of authorization actions mapping(uint32 => uint256) authorizationActionCaps; - constructor(Coordinator _coordinator, IERC20 _feeToken) - Subscription(_coordinator, _feeToken){ - - } - + /** + * @notice Sets the coordinator and fee token contracts + * @dev The coordinator and fee token contracts cannot be zero addresses + * @param _coordinator The address of the coordinator contract + * @param _feeToken The address of the fee token contract + */ + constructor( + Coordinator _coordinator, + IERC20 _feeToken, + address _beneficiary + ) Subscription(_coordinator, _feeToken, _beneficiary) {} + + /** + * @notice Pays for a subscription and increases the authorization actions cap + * @param subscriptionId The ID of the subscription + */ function paySubscriptionFor(uint32 subscriptionId) public virtual override { super.paySubscriptionFor(subscriptionId); authorizationActionCaps[subscriptionId] += DEFAULT_CAP; } + + /** + * @notice Returns the authorization actions cap for a given ritual and spender + * @param ritualId The ID of the ritual + * @param spender The address of the spender + * @return The authorization actions cap + */ + function authorizationActionsCap( + uint32 ritualId, + address spender + ) public view returns (uint256) { + return authorizationActionCaps[subscribers[lookupKey(ritualId, spender)]]; + } } diff --git a/contracts/contracts/coordination/SubscriptionManager.sol b/contracts/contracts/coordination/SubscriptionManager.sol deleted file mode 100644 index d1c43c9ec..000000000 --- a/contracts/contracts/coordination/SubscriptionManager.sol +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.0; - -import "./ManagedAllowList.sol"; - -contract SubscriptionManager is ManagedAllowList { - struct Subscription { - address subscriber; - uint256 amount; - uint256 period; // Represents the end timestamp of the subscription - } - - mapping(bytes32 => Subscription) public subscriptions; - - event SubscriptionCreated( - uint32 indexed ritualId, - address indexed subscriber, - uint256 amount, - uint256 period - ); - event SubscriptionTerminated(uint32 indexed ritualId, address indexed subscriber); - event SubscriptionRenewed( - uint32 indexed ritualId, - address indexed subscriber, - uint256 amount, - uint256 period - ); - - function createSubscription( - uint32 ritualId, - address _subscriber, - uint256 _amount, - uint256 _period - ) external onlyCohortAuthority(ritualId) { - bytes32 key = lookupKey(ritualId, _subscriber); - require( - subscriptions[key].subscriber == address(0), - "Subscriber already has an active subscription" - ); - - Subscription memory newSubscription = Subscription({ - subscriber: _subscriber, - amount: _amount, - period: _period - }); - - subscriptions[key] = newSubscription; - - emit SubscriptionCreated(ritualId, _subscriber, _amount, _period); - } - - function terminateSubscription( - uint32 ritualId, - address _subscriber - ) external onlySubscriber(ritualId, _subscriber) { - bytes32 key = lookupKey(ritualId, _subscriber); - require( - subscriptions[key].subscriber != address(0), - "No active subscription found for the subscriber" - ); - - delete subscriptions[key]; - - emit SubscriptionTerminated(ritualId, _subscriber); - - // TODO: Return the _amount to the subscriber. Is it a native asset or ERC20? - } - - // TODO: Handle a native asset or ERC20 as _amount - function renewSubscription( - uint32 ritualId, - address _subscriber, - uint256 _amount, - uint256 _period - ) external onlyCohortAuthority(ritualId) { - bytes32 key = lookupKey(ritualId, _subscriber); - require( - subscriptions[key].subscriber != address(0), - "No active subscription found for the subscriber" - ); - - Subscription storage subscription = subscriptions[key]; - subscription.amount = _amount; - subscription.period = _period; - - emit SubscriptionRenewed(ritualId, _subscriber, _amount, _period); - } - - function _beforeIsAuthorized( - uint32 ritualId, - bytes memory evidence, - bytes memory ciphertextHeader - ) internal view override { - bytes32 digest = keccak256(ciphertextHeader); - address recoveredAddress = digest.toEthSignedMessageHash().recover(evidence); - Subscription memory subscription = subscriptions[lookupKey(ritualId, recoveredAddress)]; - require( - subscription.subscriber != address(0) && subscription.period >= block.timestamp, - "Subscriber is not authorized or subscription has expired" - ); - } - - // TODO: What do I do with the `bool value`? - function _beforeSetAuthorization( - uint32 ritualId, - address[] calldata addresses, - bool value - ) internal override { - for (uint256 i = 0; i < addresses.length; i++) { - Subscription memory subscription = subscriptions[lookupKey(ritualId, addresses[i])]; - require( - subscription.subscriber != address(0) && subscription.period >= block.timestamp, - "Subscriber is not authorized or subscription has expired" - ); - } - } -} From 8257adf3c6b701d7f6183abf21aaa2e1d37f257d Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Fri, 19 Apr 2024 13:27:40 +0200 Subject: [PATCH 014/105] wip: more tests, yes yes --- .../coordination/ManagedAllowList.sol | 4 +- .../contracts/coordination/Subscription.sol | 5 + .../test/EncryptionAuthorizerTestSet.sol | 18 +++ tests/test_managed_allow_list.py | 125 ++++++++++-------- 4 files changed, 99 insertions(+), 53 deletions(-) diff --git a/contracts/contracts/coordination/ManagedAllowList.sol b/contracts/contracts/coordination/ManagedAllowList.sol index 2b405f585..80393cb46 100644 --- a/contracts/contracts/coordination/ManagedAllowList.sol +++ b/contracts/contracts/coordination/ManagedAllowList.sol @@ -91,7 +91,9 @@ contract ManagedAllowList is GlobalAllowList { ) internal view override { for (uint256 i = 0; i < addresses.length; i++) { require( - authActions[ritualId] < subscription.authorizationActionsCap(ritualId, addresses[i]) + authActions[ritualId] < + subscription.authorizationActionsCap(ritualId, addresses[i]), + "Authorization cap exceeded" ); } } diff --git a/contracts/contracts/coordination/Subscription.sol b/contracts/contracts/coordination/Subscription.sol index 03b53c309..ed34b6a84 100644 --- a/contracts/contracts/coordination/Subscription.sol +++ b/contracts/contracts/coordination/Subscription.sol @@ -93,6 +93,9 @@ abstract contract Subscription { subscribers[lookupKey(ritualId, msg.sender)] = subscriptionId; numberOfSubscriptions += 1; + + // TODO: Emit event? + return subscriptionId; } @@ -108,6 +111,8 @@ abstract contract Subscription { sub.expiration += baseExpiration(); feeToken.safeTransferFrom(msg.sender, address(this), amount); + + // TODO: Emit event? } /** diff --git a/contracts/test/EncryptionAuthorizerTestSet.sol b/contracts/test/EncryptionAuthorizerTestSet.sol index 86b074f4c..72672dfd2 100644 --- a/contracts/test/EncryptionAuthorizerTestSet.sol +++ b/contracts/test/EncryptionAuthorizerTestSet.sol @@ -19,3 +19,21 @@ contract CoordinatorForEncryptionAuthorizerMock { isRitualActive[ritualId] = false; } } + + +contract SubscriptionForManagedAllowListMock { + + uint32 public numberOfRituals; + mapping(uint32 => address) public getAuthority; + mapping(uint32 => bool) public isRitualActive; + + function mockNewRitual(address authority) external { + getAuthority[numberOfRituals] = authority; + isRitualActive[numberOfRituals] = true; + numberOfRituals += 1; + } + + function mockEndRitual(uint32 ritualId) external { + isRitualActive[ritualId] = false; + } +} diff --git a/tests/test_managed_allow_list.py b/tests/test_managed_allow_list.py index 422eeca0d..e0fc53945 100644 --- a/tests/test_managed_allow_list.py +++ b/tests/test_managed_allow_list.py @@ -1,8 +1,9 @@ +import ape import pytest - RITUAL_ID = 0 ADMIN_CAP = 5 +ERC20_SUPPLY = 10**24 @pytest.fixture(scope="module") @@ -25,6 +26,11 @@ def encryptor(accounts): return accounts[3] +@pytest.fixture(scope="module") +def beneficiary(accounts): + return accounts[4] + + @pytest.fixture() def coordinator(project, deployer): contract = project.CoordinatorForEncryptionAuthorizerMock.deploy( @@ -34,8 +40,22 @@ def coordinator(project, deployer): @pytest.fixture() -def brand_new_managed_allow_list(project, coordinator, authority): - return project.ManagedAllowList.deploy(coordinator.address, sender=authority) +def fee_token(project, deployer): + return project.TestToken.deploy(ERC20_SUPPLY, sender=deployer) + + +@pytest.fixture() +def subscription(project, coordinator, fee_token, beneficiary, authority): + return project.UpfrontSubscriptionWithEncryptorsCap.deploy( + coordinator.address, fee_token.address, beneficiary, sender=authority + ) + + +@pytest.fixture() +def brand_new_managed_allow_list(project, coordinator, subscription, authority): + return project.ManagedAllowList.deploy( + coordinator.address, subscription.address, sender=authority + ) @pytest.fixture() @@ -44,68 +64,69 @@ def managed_allow_list(brand_new_managed_allow_list, coordinator, deployer, auth return brand_new_managed_allow_list -def test_initial_parameters(brand_new_managed_allow_list, coordinator, admin, encryptor): - assert brand_new_managed_allow_list.coordinator() == coordinator.address - assert not brand_new_managed_allow_list.getAllowance(RITUAL_ID, admin.address) - assert not brand_new_managed_allow_list.isAddressAuthorized(RITUAL_ID, encryptor.address) +def test_initial_parameters(managed_allow_list, coordinator, admin, encryptor): + assert managed_allow_list.coordinator() == coordinator.address + assert not managed_allow_list.getAllowance(RITUAL_ID, admin.address) + assert not managed_allow_list.authActions(RITUAL_ID) -# TODO: Missing checks for events below +def test_add_administrators(managed_allow_list, authority, admin): + # Only authority can add administrators + with ape.reverts("Only cohort authority is permitted"): + managed_allow_list.addAdministrators(RITUAL_ID, [admin.address], ADMIN_CAP, sender=admin) -def test_add_administrator(managed_allow_list, authority, admin): - managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) - assert managed_allow_list.getAllowance(RITUAL_ID, admin) == ADMIN_CAP - - -def test_remove_administrator(managed_allow_list, authority, admin): - managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) - managed_allow_list.removeAdministrators(RITUAL_ID, [admin], sender=authority) - assert not managed_allow_list.getAllowance(RITUAL_ID, admin) + tx = managed_allow_list.addAdministrators( + RITUAL_ID, [admin.address], ADMIN_CAP, sender=authority + ) + assert tx.events == [ + managed_allow_list.AdministratorCapSet(RITUAL_ID, admin.address, ADMIN_CAP) + ] + assert managed_allow_list.getAllowance(RITUAL_ID, admin.address) == ADMIN_CAP + assert managed_allow_list.authActions(RITUAL_ID) == 1 -def test_authorize(managed_allow_list, authority, admin, encryptor): - managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) - managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=admin) - assert managed_allow_list.isAddressAuthorized(RITUAL_ID, encryptor) +def test_remove_administrators(managed_allow_list, authority, admin): + managed_allow_list.addAdministrators(RITUAL_ID, [admin.address], ADMIN_CAP, sender=authority) + assert managed_allow_list.authActions(RITUAL_ID) == 1 + # Only authority can remove administrators + with ape.reverts("Only cohort authority is permitted"): + managed_allow_list.removeAdministrators(RITUAL_ID, [admin.address], sender=admin) -def test_deauthorize(managed_allow_list, admin, authority, encryptor): - managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) - managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=admin) - managed_allow_list.deauthorize(RITUAL_ID, [encryptor], sender=admin) - assert not managed_allow_list.isAddressAuthorized(RITUAL_ID, encryptor) + tx = managed_allow_list.removeAdministrators(RITUAL_ID, [admin.address], sender=authority) + assert tx.events == [managed_allow_list.AdministratorCapSet(RITUAL_ID, admin.address, 0)] + assert managed_allow_list.getAllowance(RITUAL_ID, admin.address) == 0 + # Auth actions may only increase + assert managed_allow_list.authActions(RITUAL_ID) == 2 -def test_only_authority_can_add_administrator(managed_allow_list, admin, authority, encryptor): - with pytest.raises(Exception): - managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=admin) +def test_authorize(managed_allow_list, subscription, fee_token, authority, admin, encryptor): managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) - assert managed_allow_list.getAllowance(RITUAL_ID, admin) + assert managed_allow_list.getAllowance(RITUAL_ID, admin) == ADMIN_CAP + # Authorization requires a valid and paid for subscription + with ape.reverts("Authorization cap exceeded"): + managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=admin) -def test_only_authority_can_remove_administrator(managed_allow_list, admin, authority): - managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) - with pytest.raises(Exception): - managed_allow_list.removeAdministrators(RITUAL_ID, [admin], sender=admin) - managed_allow_list.removeAdministrators(RITUAL_ID, [admin], sender=authority) - assert not managed_allow_list.getAllowance(RITUAL_ID, admin) + cost = subscription.subscriptionFee() + fee_token.approve(subscription.address, cost, sender=authority) + # subscription_id = subscription.newSubscription(RITUAL_ID, sender=authority) + # assert subscription_id == 0 + # assert subscription.subscription.authorizationActionsCap(RITUAL_ID, admin) == 1 + # assert tx1.events == [managed_allow_list.AddressAuthorizationSet(RITUAL_ID, admin, True)] + # # Only administrators can authorize encryptors + # with ape.reverts("Only administrator is permitted"): + # managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=encryptor) -def test_only_administrator_can_authorize(managed_allow_list, admin, authority, encryptor): - managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) - with pytest.raises(Exception): - managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=encryptor) - with pytest.raises(Exception): - managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=authority) - managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=admin) - assert managed_allow_list.isAddressAuthorized(RITUAL_ID, encryptor) + # tx = managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=admin) + # assert tx.events == [managed_allow_list.AddressAuthorizationSet(RITUAL_ID, encryptor, True)] + # + # assert managed_allow_list.isAddressAuthorized(RITUAL_ID, encryptor) -def test_only_administrator_can_deauthorize(managed_allow_list, admin, authority, encryptor): - managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) - with pytest.raises(Exception): - managed_allow_list.deauthorize(RITUAL_ID, [encryptor], sender=encryptor) - with pytest.raises(Exception): - managed_allow_list.deauthorize(RITUAL_ID, [encryptor], sender=authority) - managed_allow_list.deauthorize(RITUAL_ID, [encryptor], sender=admin) - assert not managed_allow_list.isAddressAuthorized(RITUAL_ID, encryptor) +# def test_deauthorize(managed_allow_list, admin, authority, encryptor): +# managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) +# managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=admin) +# managed_allow_list.deauthorize(RITUAL_ID, [encryptor], sender=admin) +# assert not managed_allow_list.isAddressAuthorized(RITUAL_ID, encryptor) From 746e28575cd7cd73dfc4fbab22d5fde755904864 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Fri, 19 Apr 2024 15:29:44 +0200 Subject: [PATCH 015/105] wip: test test test --- tests/test_managed_allow_list.py | 60 ++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/tests/test_managed_allow_list.py b/tests/test_managed_allow_list.py index e0fc53945..4a416afb8 100644 --- a/tests/test_managed_allow_list.py +++ b/tests/test_managed_allow_list.py @@ -100,7 +100,9 @@ def test_remove_administrators(managed_allow_list, authority, admin): assert managed_allow_list.authActions(RITUAL_ID) == 2 -def test_authorize(managed_allow_list, subscription, fee_token, authority, admin, encryptor): +def test_authorize( + managed_allow_list, subscription, fee_token, deployer, authority, admin, encryptor +): managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) assert managed_allow_list.getAllowance(RITUAL_ID, admin) == ADMIN_CAP @@ -109,24 +111,38 @@ def test_authorize(managed_allow_list, subscription, fee_token, authority, admin managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=admin) cost = subscription.subscriptionFee() - fee_token.approve(subscription.address, cost, sender=authority) - # subscription_id = subscription.newSubscription(RITUAL_ID, sender=authority) - # assert subscription_id == 0 - # assert subscription.subscription.authorizationActionsCap(RITUAL_ID, admin) == 1 - # assert tx1.events == [managed_allow_list.AddressAuthorizationSet(RITUAL_ID, admin, True)] - - # # Only administrators can authorize encryptors - # with ape.reverts("Only administrator is permitted"): - # managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=encryptor) - - # tx = managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=admin) - # assert tx.events == [managed_allow_list.AddressAuthorizationSet(RITUAL_ID, encryptor, True)] - # - # assert managed_allow_list.isAddressAuthorized(RITUAL_ID, encryptor) - - -# def test_deauthorize(managed_allow_list, admin, authority, encryptor): -# managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) -# managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=admin) -# managed_allow_list.deauthorize(RITUAL_ID, [encryptor], sender=admin) -# assert not managed_allow_list.isAddressAuthorized(RITUAL_ID, encryptor) + fee_token.approve(subscription.address, cost, sender=deployer) + tx = subscription.newSubscription(RITUAL_ID, sender=deployer) + assert subscription.numberOfSubscriptions() == 1 + # TODO: Fix this - Currently fails because fee_token is a mock contract + # assert tx.events == [ + # fee_token.Transfer(admin, subscription.address, cost), + # managed_allow_list.AddressAuthorizationSet(RITUAL_ID, admin, True) + # ] + assert len(tx.events) == 1 + assert subscription.authorizationActionsCap(RITUAL_ID, admin) == 1000 + + # Only administrators can authorize encryptors + with ape.reverts("Only administrator is permitted"): + managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=encryptor) + + tx = managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=admin) + assert tx.events == [managed_allow_list.AddressAuthorizationSet(RITUAL_ID, encryptor, True)] + assert managed_allow_list.isAddressAuthorized(RITUAL_ID, encryptor) + + +def test_deauthorize( + managed_allow_list, subscription, fee_token, deployer, authority, admin, encryptor +): + managed_allow_list.addAdministrators(RITUAL_ID, [admin], ADMIN_CAP, sender=authority) + cost = subscription.subscriptionFee() + fee_token.approve(subscription.address, cost, sender=deployer) + subscription.newSubscription(RITUAL_ID, sender=deployer) + assert subscription.authorizationActionsCap(RITUAL_ID, admin) == 1000 + + with ape.reverts("Only administrator is permitted"): + managed_allow_list.deauthorize(RITUAL_ID, [encryptor], sender=encryptor) + + tx = managed_allow_list.deauthorize(RITUAL_ID, [encryptor], sender=admin) + assert tx.events == [managed_allow_list.AddressAuthorizationSet(RITUAL_ID, encryptor, False)] + assert not managed_allow_list.isAddressAuthorized(RITUAL_ID, encryptor) From 6d41044732952ddd890b1673d1a6c916893cbb52 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Fri, 19 Apr 2024 15:45:49 +0200 Subject: [PATCH 016/105] wip: fix solhint --- .../coordination/GlobalAllowList.sol | 8 +- .../coordination/ManagedAllowList.sol | 5 +- .../contracts/coordination/Subscription.sol | 12 +- .../test/EncryptionAuthorizerTestSet.sol | 6 +- package-lock.json | 1865 +++++++++++------ package.json | 4 +- 6 files changed, 1209 insertions(+), 691 deletions(-) diff --git a/contracts/contracts/coordination/GlobalAllowList.sol b/contracts/contracts/coordination/GlobalAllowList.sol index db0e96c7e..93df10902 100644 --- a/contracts/contracts/coordination/GlobalAllowList.sol +++ b/contracts/contracts/coordination/GlobalAllowList.sol @@ -87,7 +87,9 @@ contract GlobalAllowList is IEncryptionAuthorizer { uint32 ritualId, bytes memory evidence, bytes memory ciphertextHeader - ) internal view virtual {} + ) internal view virtual { + // solhint-disable-previous-line no-empty-blocks + } /** * @notice Checks if an address is authorized for a ritual @@ -119,7 +121,9 @@ contract GlobalAllowList is IEncryptionAuthorizer { uint32 ritualId, address[] calldata addresses, bool value - ) internal view virtual {} + ) internal view virtual { + // solhint-disable-previous-line no-empty-blocks + } /** * @notice Authorizes a list of addresses for a ritual diff --git a/contracts/contracts/coordination/ManagedAllowList.sol b/contracts/contracts/coordination/ManagedAllowList.sol index 80393cb46..cdfbb2dbd 100644 --- a/contracts/contracts/coordination/ManagedAllowList.sol +++ b/contracts/contracts/coordination/ManagedAllowList.sol @@ -90,8 +90,11 @@ contract ManagedAllowList is GlobalAllowList { bool value ) internal view override { for (uint256 i = 0; i < addresses.length; i++) { + // If we want to authorize an address, we need to check if the authorization cap has been exceeded + // If we want to deauthorize an address, we don't need to check the authorization cap require( - authActions[ritualId] < + !value || + authActions[ritualId] < subscription.authorizationActionsCap(ritualId, addresses[i]), "Authorization cap exceeded" ); diff --git a/contracts/contracts/coordination/Subscription.sol b/contracts/contracts/coordination/Subscription.sol index ed34b6a84..03ba5ca10 100644 --- a/contracts/contracts/coordination/Subscription.sol +++ b/contracts/contracts/coordination/Subscription.sol @@ -23,8 +23,8 @@ abstract contract Subscription { address subscriber; } - Coordinator coordinator; - IERC20 feeToken; + Coordinator public coordinator; + IERC20 public feeToken; // Mapping from subscription ID to subscription info mapping(uint32 => SubscriptionInfo) public subscriptions; @@ -122,7 +122,7 @@ abstract contract Subscription { * @return True if the spender can spend from the subscription, false otherwise */ function canSpendFromSubscription( - uint32 subscriptionId, + uint32 subscriptionId, // TODO: Currently unused address spender ) public returns (bool) { // By default, only coordinator can spend from subscription @@ -135,7 +135,7 @@ abstract contract Subscription { * @param amount The amount to spend */ function spendFromSubscription(uint32 subscriptionId, uint256 amount) external { - require(canSpendFromSubscription(subscriptionId, msg.sender)); + require(canSpendFromSubscription(subscriptionId, msg.sender), "Unauthorized spender"); feeToken.safeTransferFrom(address(this), msg.sender, amount); } @@ -175,10 +175,10 @@ abstract contract Subscription { * @dev This contract extends the Subscription contract and introduces a cap on the number of encryptors. */ contract UpfrontSubscriptionWithEncryptorsCap is Subscription { - uint256 constant DEFAULT_CAP = 1000; + uint256 public constant DEFAULT_CAP = 1000; // Mapping from subscription ID to the number of authorization actions - mapping(uint32 => uint256) authorizationActionCaps; + mapping(uint32 => uint256) public authorizationActionCaps; /** * @notice Sets the coordinator and fee token contracts diff --git a/contracts/test/EncryptionAuthorizerTestSet.sol b/contracts/test/EncryptionAuthorizerTestSet.sol index 72672dfd2..77fff81b1 100644 --- a/contracts/test/EncryptionAuthorizerTestSet.sol +++ b/contracts/test/EncryptionAuthorizerTestSet.sol @@ -2,9 +2,7 @@ pragma solidity ^0.8.0; - contract CoordinatorForEncryptionAuthorizerMock { - uint32 public numberOfRituals; mapping(uint32 => address) public getAuthority; mapping(uint32 => bool) public isRitualActive; @@ -17,12 +15,10 @@ contract CoordinatorForEncryptionAuthorizerMock { function mockEndRitual(uint32 ritualId) external { isRitualActive[ritualId] = false; - } + } } - contract SubscriptionForManagedAllowListMock { - uint32 public numberOfRituals; mapping(uint32 => address) public getAuthority; mapping(uint32 => bool) public isRitualActive; diff --git a/package-lock.json b/package-lock.json index 3ad45521b..d8369fd10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@nucypher/nucypher-contracts", - "version": "0.20.0", + "version": "0.22.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@nucypher/nucypher-contracts", - "version": "0.20.0", + "version": "0.22.0", "license": "AGPL-3.0-or-later", "devDependencies": { "@typescript-eslint/eslint-plugin": "^6.9.0", @@ -34,89 +34,18 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/helper-validator-identifier": { "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", @@ -127,14 +56,15 @@ } }, "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -211,10 +141,26 @@ "node": ">=4" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", "cpu": [ "arm" ], @@ -228,9 +174,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", "cpu": [ "arm64" ], @@ -244,9 +190,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", "cpu": [ "x64" ], @@ -260,9 +206,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", "cpu": [ "arm64" ], @@ -276,9 +222,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", "cpu": [ "x64" ], @@ -292,9 +238,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", "cpu": [ "arm64" ], @@ -308,9 +254,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", "cpu": [ "x64" ], @@ -324,9 +270,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", "cpu": [ "arm" ], @@ -340,9 +286,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", "cpu": [ "arm64" ], @@ -356,9 +302,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", "cpu": [ "ia32" ], @@ -372,9 +318,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", "cpu": [ "loong64" ], @@ -388,9 +334,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", "cpu": [ "mips64el" ], @@ -404,9 +350,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", "cpu": [ "ppc64" ], @@ -420,9 +366,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", "cpu": [ "riscv64" ], @@ -436,9 +382,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", "cpu": [ "s390x" ], @@ -452,9 +398,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", "cpu": [ "x64" ], @@ -468,9 +414,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", "cpu": [ "x64" ], @@ -484,9 +430,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", "cpu": [ "x64" ], @@ -500,9 +446,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", "cpu": [ "x64" ], @@ -516,9 +462,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", "cpu": [ "arm64" ], @@ -532,9 +478,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", "cpu": [ "ia32" ], @@ -548,9 +494,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", "cpu": [ "x64" ], @@ -588,9 +534,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -610,29 +556,73 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", - "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -647,9 +637,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, "node_modules/@jest/schemas": { @@ -740,6 +730,214 @@ "node": ">=12" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.3.tgz", + "integrity": "sha512-X9alQ3XM6I9IlSlmC8ddAvMSyG1WuHk5oUnXGw+yUBs3BFoTizmG1La/Gr8fVJvDWAq+zlYTZ9DBgrlKRVY06g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.3.tgz", + "integrity": "sha512-eQK5JIi+POhFpzk+LnjKIy4Ks+pwJ+NXmPxOCSvOKSNRPONzKuUvWE+P9JxGZVxrtzm6BAYMaL50FFuPe0oWMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.3.tgz", + "integrity": "sha512-Od4vE6f6CTT53yM1jgcLqNfItTsLt5zE46fdPaEmeFHvPs5SjZYlLpHrSiHEKR1+HdRfxuzXHjDOIxQyC3ptBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.3.tgz", + "integrity": "sha512-0IMAO21axJeNIrvS9lSe/PGthc8ZUS+zC53O0VhF5gMxfmcKAP4ESkKOCwEi6u2asUrt4mQv2rjY8QseIEb1aw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.3.tgz", + "integrity": "sha512-ge2DC7tHRHa3caVEoSbPRJpq7azhG+xYsd6u2MEnJ6XzPSzQsTKyXvh6iWjXRf7Rt9ykIUWHtl0Uz3T6yXPpKw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.14.3.tgz", + "integrity": "sha512-ljcuiDI4V3ySuc7eSk4lQ9wU8J8r8KrOUvB2U+TtK0TiW6OFDmJ+DdIjjwZHIw9CNxzbmXY39wwpzYuFDwNXuw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.3.tgz", + "integrity": "sha512-Eci2us9VTHm1eSyn5/eEpaC7eP/mp5n46gTRB3Aar3BgSvDQGJZuicyq6TsH4HngNBgVqC5sDYxOzTExSU+NjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.3.tgz", + "integrity": "sha512-UrBoMLCq4E92/LCqlh+blpqMz5h1tJttPIniwUgOFJyjWI1qrtrDhhpHPuFxULlUmjFHfloWdixtDhSxJt5iKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.3.tgz", + "integrity": "sha512-5aRjvsS8q1nWN8AoRfrq5+9IflC3P1leMoy4r2WjXyFqf3qcqsxRCfxtZIV58tCxd+Yv7WELPcO9mY9aeQyAmw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.3.tgz", + "integrity": "sha512-sk/Qh1j2/RJSX7FhEpJn8n0ndxy/uf0kI/9Zc4b1ELhqULVdTfN6HL31CDaTChiBAOgLcsJ1sgVZjWv8XNEsAQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.3.tgz", + "integrity": "sha512-jOO/PEaDitOmY9TgkxF/TQIjXySQe5KVYB57H/8LRP/ux0ZoO8cSHCX17asMSv3ruwslXW/TLBcxyaUzGRHcqg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.3.tgz", + "integrity": "sha512-8ybV4Xjy59xLMyWo3GCfEGqtKV5M5gCSrZlxkPGvEPCGDLNla7v48S662HSGwRd6/2cSneMQWiv+QzcttLrrOA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.3.tgz", + "integrity": "sha512-s+xf1I46trOY10OqAtZ5Rm6lzHre/UiLA1J2uOhCFXWkbZrJRkYBPO6FhvGfHmdtQ3Bx793MNa7LvoWFAm93bg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.3.tgz", + "integrity": "sha512-+4h2WrGOYsOumDQ5S2sYNyhVfrue+9tc9XcLWLh+Kw3UOxAvrfOrSMFon60KspcDdytkNDh7K2Vs6eMaYImAZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.3.tgz", + "integrity": "sha512-T1l7y/bCeL/kUwh9OD4PQT4aM7Bq43vX05htPJJ46RTI4r5KNt6qJRzAfNfM+OYMNEVBWQzR2Gyk+FXLZfogGw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.3.tgz", + "integrity": "sha512-/BypzV0H1y1HzgYpxqRaXGBRqfodgoBBCcsrujT6QRcakDQdfU+Lq9PENPh5jB4I44YWq+0C2eHsHya+nZY1sA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -759,13 +957,10 @@ } }, "node_modules/@solidity-parser/parser": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz", - "integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==", - "dev": true, - "dependencies": { - "antlr4ts": "^0.5.0-alpha.4" - } + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.17.0.tgz", + "integrity": "sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw==", + "dev": true }, "node_modules/@szmarczak/http-timer": { "version": "5.0.1", @@ -780,30 +975,36 @@ } }, "node_modules/@types/chai": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.9.tgz", - "integrity": "sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg==", + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.14.tgz", + "integrity": "sha512-Wj71sXE4Q4AkGdG9Tvq1u/fquNz9EdG4LIJMwVVII7ashjD/8cf8fyIfJAjRr6YcsXnSE8cOGQPq1gqeR8z+3w==", "dev": true }, "node_modules/@types/chai-subset": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.4.tgz", - "integrity": "sha512-CCWNXrJYSUIojZ1149ksLl3AN9cmZ5djf+yUoVVV+NuYrtydItQVlL2ZDqyC6M6O9LWRnVf8yYDxbXHO2TfQZg==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.5.tgz", + "integrity": "sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A==", "dev": true, "dependencies": { "@types/chai": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, "node_modules/@types/http-cache-semantics": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz", - "integrity": "sha512-V46MYLFp08Wf2mmaBhvgjStM3tPa+2GAdy/iqoX+noX1//zje2x4XmrIU0cAwyClATsTmahbtoQ2EwP7I5WSiA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", - "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/json5": { @@ -813,31 +1014,31 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.8.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz", - "integrity": "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==", + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.9.0.tgz", - "integrity": "sha512-lgX7F0azQwRPB7t7WAyeHWVfW1YJ9NIgd9mvGhfQpRY56X6AVf8mwM8Wol+0z4liE7XX3QOt8MN1rUKCfSjRIA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.9.0", - "@typescript-eslint/type-utils": "6.9.0", - "@typescript-eslint/utils": "6.9.0", - "@typescript-eslint/visitor-keys": "6.9.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -863,16 +1064,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.0.tgz", - "integrity": "sha512-GZmjMh4AJ/5gaH4XF2eXA8tMnHWP+Pm1mjQR2QN4Iz+j/zO04b9TOvJYOX2sCNIQHtRStKTxRY1FX7LhpJT4Gw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.9.0", - "@typescript-eslint/types": "6.9.0", - "@typescript-eslint/typescript-estree": "6.9.0", - "@typescript-eslint/visitor-keys": "6.9.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "engines": { @@ -892,13 +1093,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.9.0.tgz", - "integrity": "sha512-1R8A9Mc39n4pCCz9o79qRO31HGNDvC7UhPhv26TovDsWPBDx+Sg3rOZdCELIA3ZmNoWAuxaMOT7aWtGRSYkQxw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.0", - "@typescript-eslint/visitor-keys": "6.9.0" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -909,13 +1110,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.9.0.tgz", - "integrity": "sha512-XXeahmfbpuhVbhSOROIzJ+b13krFmgtc4GlEuu1WBT+RpyGPIA4Y/eGnXzjbDj5gZLzpAXO/sj+IF/x2GtTMjQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.9.0", - "@typescript-eslint/utils": "6.9.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -936,9 +1137,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.9.0.tgz", - "integrity": "sha512-+KB0lbkpxBkBSiVCuQvduqMJy+I1FyDbdwSpM3IoBS7APl4Bu15lStPjgBIdykdRqQNYqYNMa8Kuidax6phaEw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -949,16 +1150,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.0.tgz", - "integrity": "sha512-NJM2BnJFZBEAbCfBP00zONKXvMqihZCrmwCaik0UhLr0vAgb6oguXxLX1k00oQyD+vZZ+CJn3kocvv2yxm4awQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.0", - "@typescript-eslint/visitor-keys": "6.9.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -976,17 +1178,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.9.0.tgz", - "integrity": "sha512-5Wf+Jsqya7WcCO8me504FBigeQKVLAMPmUzYgDbWchINNh1KJbxCgVya3EQ2MjvJMVeXl3pofRmprqX6mfQkjQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.9.0", - "@typescript-eslint/types": "6.9.0", - "@typescript-eslint/typescript-estree": "6.9.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "engines": { @@ -1001,12 +1203,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.0.tgz", - "integrity": "sha512-dGtAfqjV6RFOtIP8I0B4ZTBRrlTT8NHHlZZSchQx3qReaoDeXhYM++M4So2AgFK9ZB0emRPA6JI1HkafzA2Ibg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.0", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -1119,9 +1321,9 @@ } }, "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1140,9 +1342,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", - "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "dev": true, "engines": { "node": ">=0.4.0" @@ -1189,20 +1391,14 @@ } }, "node_modules/antlr4": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.1.tgz", - "integrity": "sha512-kiXTspaRYvnIArgE97z5YVVf/cDVQABr3abFRR6mE7yesLMkgu4ujuyV/sgxafQ8wgve0DJQUJ38Z8tkgA2izA==", + "version": "4.13.1-patch-1", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.1-patch-1.tgz", + "integrity": "sha512-OjFLWWLzDMV9rdFhpvroCWR4ooktNg9/nvVYSA5z28wuVpU36QUNuioR1XLnQtcjVlf8npjyz593PxnU/f/Cow==", "dev": true, "engines": { "node": ">=16" } }, - "node_modules/antlr4ts": { - "version": "0.5.0-alpha.4", - "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", - "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", - "dev": true - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1210,28 +1406,32 @@ "dev": true }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/array-includes": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", - "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" }, "engines": { @@ -1251,16 +1451,17 @@ } }, "node_modules/array.prototype.findlastindex": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", - "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -1306,17 +1507,18 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", - "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" }, "engines": { @@ -1351,10 +1553,13 @@ } }, "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -1369,13 +1574,12 @@ "dev": true }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -1390,10 +1594,22 @@ "node": ">=8" } }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/builtins": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", - "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", + "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", "dev": true, "dependencies": { "semver": "^7.0.0" @@ -1436,14 +1652,19 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1459,9 +1680,9 @@ } }, "node_modules/chai": { - "version": "4.3.10", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", - "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", "dev": true, "dependencies": { "assertion-error": "^1.1.0", @@ -1537,6 +1758,12 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/confbox": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", + "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", + "dev": true + }, "node_modules/config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", @@ -1587,6 +1814,57 @@ "node": ">= 8" } }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -1668,17 +1946,20 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-properties": { @@ -1747,50 +2028,57 @@ } }, "node_modules/es-abstract": { - "version": "1.22.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", - "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.2", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.5", - "es-set-tostringtag": "^2.0.1", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.2", - "get-symbol-description": "^1.0.0", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", "has-symbols": "^1.0.3", - "hasown": "^2.0.0", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", + "is-shared-array-buffer": "^1.0.3", "is-string": "^1.0.7", - "is-typed-array": "^1.1.12", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "safe-array-concat": "^1.0.1", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.8", - "string.prototype.trimend": "^1.0.7", - "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.0", - "typed-array-byte-length": "^1.0.0", - "typed-array-byte-offset": "^1.0.0", - "typed-array-length": "^1.0.4", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.13" + "which-typed-array": "^1.1.15" }, "engines": { "node": ">= 0.4" @@ -1799,15 +2087,48 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", - "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", - "has-tostringtag": "^1.0.0", - "hasown": "^2.0.0" + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -1840,9 +2161,9 @@ } }, "node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", "dev": true, "hasInstallScript": true, "bin": { @@ -1852,28 +2173,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" } }, "node_modules/escape-string-regexp": { @@ -1889,16 +2211,16 @@ } }, "node_modules/eslint": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", - "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.52.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -1943,10 +2265,25 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-compat-utils": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.0.tgz", + "integrity": "sha512-dc6Y8tzEcSYZMHa+CMPLi/hyo1FzNeonbhJL7Ol0ccuKQkwopJcJBA9YL/xmMTLU1eKigXo9vj9nALElWYSowg==", + "dev": true, + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, "node_modules/eslint-config-prettier": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", - "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -1976,9 +2313,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", - "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", "dev": true, "dependencies": { "debug": "^3.2.7" @@ -2002,13 +2339,14 @@ } }, "node_modules/eslint-plugin-es-x": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.2.0.tgz", - "integrity": "sha512-9dvv5CcvNjSJPqnS5uZkqb3xmbeqRLnvXKK7iI5+oK/yTusyc46zbBZKENGsOfojm/mKfszyZb+wNqNPAPeGXA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.6.0.tgz", + "integrity": "sha512-I0AmeNgevgaTR7y2lrVCJmGYF0rjoznpDvqV/kIkZSZbZ8Rw3eu4cGlvBBULScfkSOCzqKbff5LR4CNrV7mZHA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.1.2", - "@eslint-community/regexpp": "^4.6.0" + "@eslint-community/regexpp": "^4.6.0", + "eslint-compat-utils": "^0.5.0" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -2021,9 +2359,9 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", - "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "dependencies": { "array-includes": "^3.1.7", @@ -2042,7 +2380,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -2051,6 +2389,16 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint-plugin-import/node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -2072,6 +2420,18 @@ "node": ">=0.10.0" } }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-import/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -2082,16 +2442,18 @@ } }, "node_modules/eslint-plugin-n": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.2.0.tgz", - "integrity": "sha512-AQER2jEyQOt1LG6JkGJCCIFotzmlcCZFur2wdKrp1JX2cNotC7Ae0BcD/4lLv3lUAArM9uNS8z/fsvXTd0L71g==", + "version": "16.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz", + "integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "builtins": "^5.0.1", - "eslint-plugin-es-x": "^7.1.0", + "eslint-plugin-es-x": "^7.5.0", "get-tsconfig": "^4.7.0", + "globals": "^13.24.0", "ignore": "^5.2.4", + "is-builtin-module": "^3.2.1", "is-core-module": "^2.12.1", "minimatch": "^3.1.2", "resolve": "^1.22.2", @@ -2103,8 +2465,30 @@ "funding": { "url": "https://github.com/sponsors/mysticatea" }, - "peerDependencies": { - "eslint": ">=7.0.0" + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-n/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, "node_modules/eslint-plugin-promise": { @@ -2147,6 +2531,28 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -2219,9 +2625,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -2259,9 +2665,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -2308,9 +2714,9 @@ } }, "node_modules/flat-cache": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", - "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { "flatted": "^3.2.9", @@ -2318,13 +2724,13 @@ "rimraf": "^3.0.2" }, "engines": { - "node": ">=12.0.0" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, "node_modules/for-each": { @@ -2402,9 +2808,9 @@ } }, "node_modules/ganache": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/ganache/-/ganache-7.9.1.tgz", - "integrity": "sha512-Tqhd4J3cpiLeYTD6ek/zlchSB107IVPMIm4ypyg+xz1sdkeALUnYYZnmY4Bdjqj3i6QwtlZPCu7U4qKy7HlWTA==", + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/ganache/-/ganache-7.9.2.tgz", + "integrity": "sha512-7gsVVDpO9AhrFyDMWWl7SpMsPpqGcnAzjxz3k32LheIPNd64p2XsY9GYRdhWmKuryb60W1iaWPZWDkFKlbRWHA==", "bundleDependencies": [ "@trufflesuite/bigint-buffer", "keccak", @@ -6321,16 +6727,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6348,13 +6758,14 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -6364,9 +6775,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", - "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz", + "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==", "dev": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -6407,10 +6818,32 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -6525,21 +6958,21 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "dev": true, "engines": { "node": ">= 0.4" @@ -6561,12 +6994,12 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -6576,9 +7009,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "dependencies": { "function-bind": "^1.1.2" @@ -6594,9 +7027,9 @@ "dev": true }, "node_modules/http2-wrapper": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.0.tgz", - "integrity": "sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "dev": true, "dependencies": { "quick-lru": "^5.1.1", @@ -6607,9 +7040,9 @@ } }, "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { "node": ">= 4" @@ -6663,12 +7096,12 @@ "dev": true }, "node_modules/internal-slot": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", - "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -6677,14 +7110,16 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6724,6 +7159,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -6748,6 +7198,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -6794,9 +7259,9 @@ } }, "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, "engines": { "node": ">= 0.4" @@ -6855,12 +7320,15 @@ } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6897,12 +7365,12 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dev": true, "dependencies": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -6989,12 +7457,6 @@ "json5": "lib/cli.js" } }, - "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -7117,15 +7579,12 @@ } }, "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" } }, "node_modules/merge2": { @@ -7163,15 +7622,18 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -7184,15 +7646,15 @@ } }, "node_modules/mlly": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", - "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz", + "integrity": "sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==", "dev": true, "dependencies": { - "acorn": "^8.10.0", - "pathe": "^1.1.1", + "acorn": "^8.11.3", + "pathe": "^1.1.2", "pkg-types": "^1.0.3", - "ufo": "^1.3.0" + "ufo": "^1.3.2" } }, "node_modules/ms": { @@ -7202,9 +7664,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, "funding": [ { @@ -7226,9 +7688,9 @@ "dev": true }, "node_modules/normalize-url": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", - "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", + "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", "dev": true, "engines": { "node": ">=14.16" @@ -7256,13 +7718,13 @@ } }, "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", "has-symbols": "^1.0.3", "object-keys": "^1.1.1" }, @@ -7274,14 +7736,15 @@ } }, "node_modules/object.fromentries": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", - "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -7291,26 +7754,28 @@ } }, "node_modules/object.groupby": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", - "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/object.values": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", - "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -7475,9 +7940,9 @@ } }, "node_modules/pathe": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", - "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", "dev": true }, "node_modules/pathval": { @@ -7508,14 +7973,14 @@ } }, "node_modules/pkg-types": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", - "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.0.tgz", + "integrity": "sha512-/RpmvKdxKf8uILTtoOhAgf30wYbP2Qw+L9p3Rvshx1JZVX+XQNZQFjlbmGHEGIm4CkVPlSn+NXmIM8+9oWQaSA==", "dev": true, "dependencies": { - "jsonc-parser": "^3.2.0", - "mlly": "^1.2.0", - "pathe": "^1.1.0" + "confbox": "^0.1.7", + "mlly": "^1.6.1", + "pathe": "^1.1.2" } }, "node_modules/pluralize": { @@ -7527,10 +7992,19 @@ "node": ">=4" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, "funding": [ { @@ -7547,9 +8021,9 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -7592,20 +8066,20 @@ } }, "node_modules/prettier-plugin-solidity": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.3.tgz", - "integrity": "sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.3.1.tgz", + "integrity": "sha512-MN4OP5I2gHAzHZG1wcuJl0FsLS3c4Cc5494bbg+6oQWBPuEamjwDvmGfFMZ6NFzsh3Efd9UUxeT7ImgjNH4ozA==", "dev": true, "dependencies": { - "@solidity-parser/parser": "^0.16.0", - "semver": "^7.3.8", - "solidity-comments-extractor": "^0.0.7" + "@solidity-parser/parser": "^0.17.0", + "semver": "^7.5.4", + "solidity-comments-extractor": "^0.0.8" }, "engines": { - "node": ">=12" + "node": ">=16" }, "peerDependencies": { - "prettier": ">=2.3.0 || >=3.0.0-alpha.0" + "prettier": ">=2.3.0" } }, "node_modules/pretty-format": { @@ -7641,9 +8115,9 @@ "dev": true }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" @@ -7712,14 +8186,15 @@ "dev": true }, "node_modules/regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -7846,18 +8321,37 @@ } }, "node_modules/rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.3.tgz", + "integrity": "sha512-ag5tTQKYsj1bhrFC9+OEWqb5O6VYgtQDO9hPDBMmIbePwhfSr+ExlcU741t8Dhw5DkPCQf6noz0jb36D6W9/hw==", "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, "bin": { "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=14.18.0", + "node": ">=18.0.0", "npm": ">=8.0.0" }, "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.14.3", + "@rollup/rollup-android-arm64": "4.14.3", + "@rollup/rollup-darwin-arm64": "4.14.3", + "@rollup/rollup-darwin-x64": "4.14.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.14.3", + "@rollup/rollup-linux-arm-musleabihf": "4.14.3", + "@rollup/rollup-linux-arm64-gnu": "4.14.3", + "@rollup/rollup-linux-arm64-musl": "4.14.3", + "@rollup/rollup-linux-powerpc64le-gnu": "4.14.3", + "@rollup/rollup-linux-riscv64-gnu": "4.14.3", + "@rollup/rollup-linux-s390x-gnu": "4.14.3", + "@rollup/rollup-linux-x64-gnu": "4.14.3", + "@rollup/rollup-linux-x64-musl": "4.14.3", + "@rollup/rollup-win32-arm64-msvc": "4.14.3", + "@rollup/rollup-win32-ia32-msvc": "4.14.3", + "@rollup/rollup-win32-x64-msvc": "4.14.3", "fsevents": "~2.3.2" } }, @@ -7885,13 +8379,13 @@ } }, "node_modules/safe-array-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", - "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -7903,23 +8397,26 @@ } }, "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -7932,29 +8429,32 @@ } }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" } }, "node_modules/set-function-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", - "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, "dependencies": { - "define-data-property": "^1.0.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -7982,14 +8482,18 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8028,14 +8532,14 @@ } }, "node_modules/solhint": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/solhint/-/solhint-4.0.0.tgz", - "integrity": "sha512-bFViMcFvhqVd/HK3Roo7xZXX5nbujS7Bxeg5vnZc9QvH0yCWCrQ38Yrn1pbAY9tlKROc6wFr+rK1mxYgYrjZgA==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/solhint/-/solhint-4.5.4.tgz", + "integrity": "sha512-Cu1XiJXub2q1eCr9kkJ9VPv1sGcmj3V7Zb76B0CoezDOB9bu3DxKIFFH7ggCl9fWpEPD6xBmRLfZrYijkVmujQ==", "dev": true, "dependencies": { - "@solidity-parser/parser": "^0.16.0", + "@solidity-parser/parser": "^0.18.0", "ajv": "^6.12.6", - "antlr4": "^4.11.0", + "antlr4": "^4.13.1-patch-1", "ast-parents": "^0.0.1", "chalk": "^4.1.2", "commander": "^10.0.0", @@ -8072,14 +8576,11 @@ "prettier-plugin-solidity": "^1.0.0-alpha.14" } }, - "node_modules/solhint/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } + "node_modules/solhint/node_modules/@solidity-parser/parser": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz", + "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==", + "dev": true }, "node_modules/solhint/node_modules/glob": { "version": "8.1.0", @@ -8113,15 +8614,15 @@ } }, "node_modules/solidity-comments-extractor": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz", - "integrity": "sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw==", + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.8.tgz", + "integrity": "sha512-htM7Vn6LhHreR+EglVMd2s+sZhcXAirB1Zlyrv5zBuTxieCvjfnRpd7iZk75m/u6NOlEyQ94C6TWbBn2cY7w8g==", "dev": true }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -8134,9 +8635,9 @@ "dev": true }, "node_modules/std-env": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.4.3.tgz", - "integrity": "sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", "dev": true }, "node_modules/string-width": { @@ -8154,14 +8655,15 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", - "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -8171,28 +8673,31 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", - "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", - "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8268,9 +8773,9 @@ } }, "node_modules/table": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", - "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", + "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", "dev": true, "dependencies": { "ajv": "^8.0.1", @@ -8312,9 +8817,9 @@ "dev": true }, "node_modules/tinybench": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", - "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.7.0.tgz", + "integrity": "sha512-Qgayeb106x2o4hNzNjsZEfFziw8IbKqtbXBjVh7VIZfBxfD5M4gWtpyx5+YTae2gJ6Y6Dz/KLepiv16RFeQWNA==", "dev": true }, "node_modules/tinypool": { @@ -8327,9 +8832,9 @@ } }, "node_modules/tinyspy": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", - "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", "dev": true, "engines": { "node": ">=14.0.0" @@ -8348,21 +8853,21 @@ } }, "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" } }, "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "dependencies": { "@types/json5": "^0.0.29", @@ -8405,29 +8910,30 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" } }, "node_modules/typed-array-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", - "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -8437,16 +8943,17 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -8456,23 +8963,29 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -8483,9 +8996,9 @@ } }, "node_modules/ufo": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.1.tgz", - "integrity": "sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", + "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", "dev": true }, "node_modules/unbox-primitive": { @@ -8519,29 +9032,29 @@ } }, "node_modules/vite": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", - "integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==", + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.9.tgz", + "integrity": "sha512-uOQWfuZBlc6Y3W/DTuQ1Sr+oIXWvqljLvS881SVmAj00d5RdgShLcuXWxseWPd4HXwiYBFW/vXHfKFeqj9uQnw==", "dev": true, "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.27", - "rollup": "^3.27.1" + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^18.0.0 || >=20.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" }, "optionalDependencies": { - "fsevents": "~2.3.2" + "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": ">= 14", + "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", @@ -8705,16 +9218,16 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", - "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" diff --git a/package.json b/package.json index be8a9464e..00b5b6588 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,10 @@ "prepublishOnly": "npm run build", "test": "vitest run", "solhint": "solhint 'contracts/**/*.sol'", + "solhint:fix": "solhint 'contracts/**/*.sol' --fix", "lint": "eslint src test --ext .ts", - "lint:fix": "eslint src test --ext .ts --fix" + "lint:fix": "eslint src test --ext .ts --fix", + "prettier:fix": "prettier --write contracts" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^6.9.0", From 0e2b4fdab16ef60210bcf6e04b83fba41d563ebc Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Fri, 19 Apr 2024 16:33:21 +0200 Subject: [PATCH 017/105] wip: add subscription tests draft --- .../contracts/coordination/Subscription.sol | 2 + tests/test_subscription.py | 74 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 tests/test_subscription.py diff --git a/contracts/contracts/coordination/Subscription.sol b/contracts/contracts/coordination/Subscription.sol index 03ba5ca10..ff73d2d77 100644 --- a/contracts/contracts/coordination/Subscription.sol +++ b/contracts/contracts/coordination/Subscription.sol @@ -166,6 +166,8 @@ abstract contract Subscription { feeToken.safeTransfer(msg.sender, refundAmount); delete subscriptions[subscriptionId]; delete subscribers[lookupKey(ritualId, msg.sender)]; + + // TODO: Emit event? } } diff --git a/tests/test_subscription.py b/tests/test_subscription.py new file mode 100644 index 000000000..bf3aaf8b8 --- /dev/null +++ b/tests/test_subscription.py @@ -0,0 +1,74 @@ +import pytest + +RITUAL_ID = 0 +ERC20_SUPPLY = 10**24 + + +@pytest.fixture(scope="module") +def deployer(accounts): + return accounts[0] + + +@pytest.fixture(scope="module") +def authority(accounts): + return accounts[1] + + +@pytest.fixture(scope="module") +def subscriber(accounts): + return accounts[2] + + +@pytest.fixture(scope="module") +def beneficiary(accounts): + return accounts[3] + + +@pytest.fixture() +def coordinator(project, deployer): + contract = project.CoordinatorForEncryptionAuthorizerMock.deploy( + sender=deployer, + ) + return contract + + +@pytest.fixture() +def fee_token(project, deployer): + return project.TestToken.deploy(ERC20_SUPPLY, sender=deployer) + + +@pytest.fixture() +def subscription(project, coordinator, fee_token, beneficiary, authority): + return project.UpfrontSubscriptionWithEncryptorsCap.deploy( + coordinator.address, fee_token.address, beneficiary, sender=authority + ) + + +def test_new_subscription(subscription, fee_token, deployer): + cost = subscription.subscriptionFee() + fee_token.approve(subscription.address, cost, sender=deployer) + tx = subscription.newSubscription(RITUAL_ID, sender=deployer) + assert subscription.numberOfSubscriptions() == 1 + # TODO: Fix this - Currently fails because fee_token is a mock contract + # assert tx.events == [ + # fee_token.Transfer(admin, subscription.address, cost), + # managed_allow_list.AddressAuthorizationSet(RITUAL_ID, admin, True) + # ] + assert len(tx.events) == 1 + assert subscription.authorizationActionsCap(RITUAL_ID, deployer) == 1000 + + +def test_cancel_subscription(subscription, fee_token, deployer): + cost = subscription.subscriptionFee() + fee_token.approve(subscription.address, cost, sender=deployer) + subscription.newSubscription(RITUAL_ID, sender=deployer) + assert subscription.numberOfSubscriptions() == 1 + assert subscription.authorizationActionsCap(RITUAL_ID, deployer) == 1000 + + subscription.cancelSubscription(RITUAL_ID, 0, sender=deployer) + # TODO: Fix assertions + # assert not subscription.subscriptions()[0] + # assert not subscription.authorizationActionsCap(RITUAL_ID, deployer) + + +# TODO: Add more tests From b568607545a7d0a6a1d18a9312f50d1ec98abddc Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Mon, 22 Apr 2024 12:16:47 +0200 Subject: [PATCH 018/105] wip: more subscription tests --- .../contracts/coordination/Subscription.sol | 67 ++++++++-- tests/lib/test_bls.py | 1 - tests/test_subscription.py | 123 +++++++++++++++--- 3 files changed, 161 insertions(+), 30 deletions(-) diff --git a/contracts/contracts/coordination/Subscription.sol b/contracts/contracts/coordination/Subscription.sol index ff73d2d77..59a418c9b 100644 --- a/contracts/contracts/coordination/Subscription.sol +++ b/contracts/contracts/coordination/Subscription.sol @@ -37,6 +37,42 @@ abstract contract Subscription { // TODO: DAO Treasury address public beneficiary; + /** + * @notice Emitted when a subscription is created + * @param subscriptionId The ID of the subscription + * @param subscriber The address of the subscriber + * @param ritualId The ID of the ritual + */ + event SubscriptionCreated( + uint32 indexed subscriptionId, + address indexed subscriber, + uint32 indexed ritualId + ); + + /** + * @notice Emitted when a subscription is cancelled + * @param subscriptionId The ID of the subscription + * @param subscriber The address of the subscriber + * @param ritualId The ID of the ritual + */ + event SubscriptionCancelled( + uint32 indexed subscriptionId, + address indexed subscriber, + uint32 indexed ritualId + ); + + /** + * @notice Emitted when a subscription is paid + * @param subscriptionId The ID of the subscription + * @param subscriber The address of the subscriber + * @param amount The amount paid + */ + event SubscriptionPaid( + uint32 indexed subscriptionId, + address indexed subscriber, + uint256 amount + ); + /** * @notice Sets the coordinator and fee token contracts * @dev The coordinator and fee token contracts cannot be zero addresses @@ -82,21 +118,17 @@ abstract contract Subscription { /** * @notice Creates a new subscription * @param ritualId The ID of the ritual - * @return The ID of the new subscription */ - function newSubscription(uint32 ritualId) external returns (uint256) { + function newSubscription(uint32 ritualId) external { uint32 subscriptionId = numberOfSubscriptions; SubscriptionInfo storage sub = subscriptions[subscriptionId]; sub.subscriber = msg.sender; paySubscriptionFor(subscriptionId); subscribers[lookupKey(ritualId, msg.sender)] = subscriptionId; - numberOfSubscriptions += 1; - // TODO: Emit event? - - return subscriptionId; + emit SubscriptionCreated(subscriptionId, msg.sender, ritualId); } /** @@ -112,7 +144,8 @@ abstract contract Subscription { feeToken.safeTransferFrom(msg.sender, address(this), amount); - // TODO: Emit event? + // TODO: We already emit SubscriptionCreated, do we need this? + emit SubscriptionPaid(subscriptionId, msg.sender, amount); } /** @@ -122,7 +155,9 @@ abstract contract Subscription { * @return True if the spender can spend from the subscription, false otherwise */ function canSpendFromSubscription( - uint32 subscriptionId, // TODO: Currently unused + // TODO: Currently unused, remove? + // solhint-disable-next-line no-unused-vars + uint32 subscriptionId, address spender ) public returns (bool) { // By default, only coordinator can spend from subscription @@ -139,8 +174,6 @@ abstract contract Subscription { feeToken.safeTransferFrom(address(this), msg.sender, amount); } - // TODO: Withdraw methods for DAO Treasury, cancel subscription, etc - /** * @notice Withdraws the contract balance to the beneficiary * @param amount The amount to withdraw @@ -157,7 +190,7 @@ abstract contract Subscription { * @param ritualId The ID of the ritual * @param subscriptionId The ID of the subscription */ - function cancelSubscription(uint32 ritualId, uint32 subscriptionId) external { + function cancelSubscription(uint32 ritualId, uint32 subscriptionId) public virtual { require( msg.sender == subscriptions[subscriptionId].subscriber, "Only the subscriber can cancel the subscription" @@ -167,7 +200,7 @@ abstract contract Subscription { delete subscriptions[subscriptionId]; delete subscribers[lookupKey(ritualId, msg.sender)]; - // TODO: Emit event? + emit SubscriptionCancelled(subscriptionId, msg.sender, ritualId); } } @@ -215,4 +248,14 @@ contract UpfrontSubscriptionWithEncryptorsCap is Subscription { ) public view returns (uint256) { return authorizationActionCaps[subscribers[lookupKey(ritualId, spender)]]; } + + /** + * @notice Cancels a subscription and deletes the authorization actions cap + * @param ritualId The ID of the ritual + * @param subscriptionId The ID of the subscription + */ + function cancelSubscription(uint32 ritualId, uint32 subscriptionId) public virtual override { + super.cancelSubscription(ritualId, subscriptionId); + delete authorizationActionCaps[subscriptionId]; + } } diff --git a/tests/lib/test_bls.py b/tests/lib/test_bls.py index 72ed8c428..e748e1a2f 100644 --- a/tests/lib/test_bls.py +++ b/tests/lib/test_bls.py @@ -1,4 +1,3 @@ -import ape import os import pytest from hexbytes import HexBytes diff --git a/tests/test_subscription.py b/tests/test_subscription.py index bf3aaf8b8..58c795664 100644 --- a/tests/test_subscription.py +++ b/tests/test_subscription.py @@ -1,7 +1,8 @@ +import ape import pytest RITUAL_ID = 0 -ERC20_SUPPLY = 10**24 +ERC20_SUPPLY = 10 ** 24 @pytest.fixture(scope="module") @@ -24,6 +25,11 @@ def beneficiary(accounts): return accounts[3] +@pytest.fixture(scope="module") +def encryptor(accounts): + return accounts[4] + + @pytest.fixture() def coordinator(project, deployer): contract = project.CoordinatorForEncryptionAuthorizerMock.deploy( @@ -44,31 +50,114 @@ def subscription(project, coordinator, fee_token, beneficiary, authority): ) -def test_new_subscription(subscription, fee_token, deployer): +def assert_new_subscription(subscription, fee_token, deployer, subscriber): cost = subscription.subscriptionFee() - fee_token.approve(subscription.address, cost, sender=deployer) - tx = subscription.newSubscription(RITUAL_ID, sender=deployer) + fee_token.transfer(subscriber, cost, sender=deployer) + + fee_token.approve(subscription.address, cost, sender=subscriber) + tx = subscription.newSubscription(RITUAL_ID, sender=subscriber) assert subscription.numberOfSubscriptions() == 1 + subscription_id = 0 # TODO: Fix this - Currently fails because fee_token is a mock contract # assert tx.events == [ # fee_token.Transfer(admin, subscription.address, cost), - # managed_allow_list.AddressAuthorizationSet(RITUAL_ID, admin, True) # ] - assert len(tx.events) == 1 - assert subscription.authorizationActionsCap(RITUAL_ID, deployer) == 1000 + assert len(tx.events) == 3 + # assert tx.events[:1] == [ + # subscription.SubscriptionPaid( + # subscription_id, subscriber, cost + # ), + # subscription.SubscriptionCreated( + # subscription_id, subscriber, RITUAL_ID + # ) + # ] + assert subscription.authorizationActionsCap(RITUAL_ID, subscriber) == 1000 + + my_subscription = subscription.subscriptions(subscription_id) + assert my_subscription.subscriber == subscriber + assert my_subscription.paidFor == cost + assert my_subscription.expiration == subscription.baseExpiration() + return subscription_id, my_subscription + + +def test_new_subscription(subscription, fee_token, deployer, subscriber): + assert_new_subscription(subscription, fee_token, deployer, subscriber) + + +def test_cancel_subscription(subscription, fee_token, deployer, subscriber, encryptor): + subscription_id, _ = assert_new_subscription(subscription, fee_token, deployer, subscriber) + + # Only the subscriber can cancel the subscription + with ape.reverts("Only the subscriber can cancel the subscription"): + subscription.cancelSubscription(RITUAL_ID, subscription_id, sender=encryptor) + + tx = subscription.cancelSubscription(RITUAL_ID, subscription_id, sender=subscriber) + assert len(tx.events) == 2 + # TODO: Fix this - Currently fails because fee_token is a mock contract + # assert tx.events == [ + # fee_token.Transfer(subscription.address, subscriber, cost), + # ] + # assert tx.events[:1] == [ + # subscription.SubscriptionCancelled( + # subscription_id, subscriber, RITUAL_ID + # ) + # ] + assert not subscription.subscriptions(subscription_id)[0] + assert not subscription.authorizationActionsCap(RITUAL_ID, subscriber) -def test_cancel_subscription(subscription, fee_token, deployer): +def test_pay_subscription(subscription, fee_token, deployer, subscriber): + subscription_id, my_subscription = assert_new_subscription(subscription, fee_token, deployer, subscriber) + assert my_subscription.paidFor == subscription.subscriptionFee() + assert my_subscription.expiration == subscription.baseExpiration() + cost = subscription.subscriptionFee() - fee_token.approve(subscription.address, cost, sender=deployer) - subscription.newSubscription(RITUAL_ID, sender=deployer) - assert subscription.numberOfSubscriptions() == 1 - assert subscription.authorizationActionsCap(RITUAL_ID, deployer) == 1000 + fee_token.transfer(subscriber, cost, sender=deployer) + assert fee_token.balanceOf(subscriber) == cost + fee_token.approve(subscription.address, cost, sender=subscriber) + + tx = subscription.paySubscriptionFor(subscription_id, sender=subscriber) + assert len(tx.events) == 2 + # TODO: Fix this - Currently fails because fee_token is a mock contract + # assert tx.events == [ + # fee_token.Transfer(subscription.address, subscriber, cost), + # ] + # assert tx.events[:1] == [ + # subscription.SubscriptionCancelled( + # subscription_id, subscriber, RITUAL_ID + # ) + # ] + # TODO: These are failing, is subscription not being updated? + # assert my_subscription.paidFor == 2 * subscription.subscriptionFee() + # assert my_subscription.expiration == 2 * subscription.baseExpiration() + + +def test_can_spend_from_subscription(subscription, fee_token, deployer, subscriber, coordinator): + subscription_id, _ = assert_new_subscription(subscription, fee_token, deployer, subscriber) + subscription_balance = fee_token.balanceOf(subscription.address) + assert subscription_balance == subscription.subscriptionFee() + + # Only the coordinator can spend from the subscription + with ape.reverts("Unauthorized spender"): + subscription.spendFromSubscription(subscription_id, subscription_balance, sender=subscriber) + + assert not fee_token.balanceOf(coordinator.address) + # TODO: How do I impersonate the coordinator contract here? + # subscription.spendFromSubscription(subscription_id, subscription_balance, sender=coordinator.address) + # assert fee_token.balanceOf(coordinator.address) == subscription_balance + # assert not fee_token.balanceOf(subscription.address) + - subscription.cancelSubscription(RITUAL_ID, 0, sender=deployer) - # TODO: Fix assertions - # assert not subscription.subscriptions()[0] - # assert not subscription.authorizationActionsCap(RITUAL_ID, deployer) +def test_withdraw_to_beneficiary(subscription, fee_token, deployer, subscriber, beneficiary): + subscription_id, _ = assert_new_subscription(subscription, fee_token, deployer, subscriber) + subscription_balance = fee_token.balanceOf(subscription.address) + assert subscription_balance == subscription.subscriptionFee() + # Only the beneficiary can withdraw from the subscription + with ape.reverts("Only the beneficiary can withdraw"): + subscription.withdrawToBeneficiary(subscription_balance, sender=deployer) -# TODO: Add more tests + assert not fee_token.balanceOf(beneficiary) + subscription.withdrawToBeneficiary(subscription_balance, sender=beneficiary) + assert fee_token.balanceOf(beneficiary) == subscription_balance + assert not fee_token.balanceOf(subscription.address) From a37d13fa50ac2be59346b5475b882d0367814f95 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Tue, 23 Apr 2024 11:21:03 +0200 Subject: [PATCH 019/105] apply pr suggestions --- .../coordination/GlobalAllowList.sol | 21 ++++------- .../coordination/ManagedAllowList.sol | 14 ++++---- .../contracts/coordination/Subscription.sol | 35 ++++++++++--------- contracts/contracts/lib/LookupKey.sol | 14 ++++++++ tests/test_managed_allow_list.py | 6 +++- tests/test_subscription.py | 4 ++- 6 files changed, 54 insertions(+), 40 deletions(-) create mode 100644 contracts/contracts/lib/LookupKey.sol diff --git a/contracts/contracts/coordination/GlobalAllowList.sol b/contracts/contracts/coordination/GlobalAllowList.sol index 93df10902..b97fb24a0 100644 --- a/contracts/contracts/coordination/GlobalAllowList.sol +++ b/contracts/contracts/coordination/GlobalAllowList.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "../lib/LookupKey.sol"; import "./IEncryptionAuthorizer.sol"; import "./Coordinator.sol"; @@ -21,6 +22,8 @@ contract GlobalAllowList is IEncryptionAuthorizer { mapping(uint32 => uint256) public authActions; + uint32 public MAX_AUTH_ACTIONS = 100; + /** * @notice Emitted when an address authorization is set * @param ritualId The ID of the ritual @@ -56,16 +59,6 @@ contract GlobalAllowList is IEncryptionAuthorizer { _; } - /** - * @notice Returns the key used to lookup authorizations - * @param ritualId The ID of the ritual - * @param encryptor The address of the encryptor - * @return The key used to lookup authorizations - */ - function lookupKey(uint32 ritualId, address encryptor) internal pure returns (bytes32) { - return keccak256(abi.encodePacked(ritualId, encryptor)); - } - /** * @notice Checks if an address is authorized for a ritual * @param ritualId The ID of the ritual @@ -73,11 +66,10 @@ contract GlobalAllowList is IEncryptionAuthorizer { * @return The authorization status */ function isAddressAuthorized(uint32 ritualId, address encryptor) public view returns (bool) { - return authorizations[lookupKey(ritualId, encryptor)]; + return authorizations[LookupKey.lookupKey(ritualId, encryptor)]; } /** - * @notice Checks if an address is authorized for a ritual * @dev This function is called before the isAuthorized function * @param ritualId The ID of the ritual * @param evidence The evidence provided @@ -111,7 +103,6 @@ contract GlobalAllowList is IEncryptionAuthorizer { } /** - * @notice Checks if an address is authorized for a ritual * @dev This function is called before the setAuthorizations function * @param ritualId The ID of the ritual * @param addresses The addresses to be authorized @@ -159,9 +150,11 @@ contract GlobalAllowList is IEncryptionAuthorizer { function setAuthorizations(uint32 ritualId, address[] calldata addresses, bool value) internal { require(coordinator.isRitualActive(ritualId), "Only active rituals can set authorizations"); + require(addresses.length <= MAX_AUTH_ACTIONS, "Too many addresses"); + _beforeSetAuthorization(ritualId, addresses, value); for (uint256 i = 0; i < addresses.length; i++) { - authorizations[lookupKey(ritualId, addresses[i])] = value; + authorizations[LookupKey.lookupKey(ritualId, addresses[i])] = value; emit AddressAuthorizationSet(ritualId, addresses[i], value); } diff --git a/contracts/contracts/coordination/ManagedAllowList.sol b/contracts/contracts/coordination/ManagedAllowList.sol index cdfbb2dbd..18fc95092 100644 --- a/contracts/contracts/coordination/ManagedAllowList.sol +++ b/contracts/contracts/coordination/ManagedAllowList.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.0; +import "../lib/LookupKey.sol"; import "./GlobalAllowList.sol"; import "./Coordinator.sol"; import {UpfrontSubscriptionWithEncryptorsCap} from "./Subscription.sol"; @@ -19,7 +20,8 @@ contract ManagedAllowList is GlobalAllowList { /** * @notice The Subscription contract used to manage authorization caps */ - UpfrontSubscriptionWithEncryptorsCap public subscription; + // TODO: Should it be updatable? + UpfrontSubscriptionWithEncryptorsCap public immutable subscription; /** * @notice Emitted when an administrator cap is set @@ -46,7 +48,6 @@ contract ManagedAllowList is GlobalAllowList { /** * @notice Checks if the sender is the authority of the ritual - * @dev This function overrides the canSetAuthorizations modifier in the GlobalAllowList contract * @param ritualId The ID of the ritual */ modifier onlyCohortAuthority(uint32 ritualId) { @@ -74,11 +75,10 @@ contract ManagedAllowList is GlobalAllowList { * @return The allowance of the administrator */ function getAllowance(uint32 ritualId, address admin) public view returns (uint256) { - return allowance[lookupKey(ritualId, admin)]; + return allowance[LookupKey.lookupKey(ritualId, admin)]; } /** - * @notice Checks if an address is authorized for a ritual * @dev This function is called before the setAuthorizations function * @param ritualId The ID of the ritual * @param addresses The addresses to be authorized @@ -92,9 +92,9 @@ contract ManagedAllowList is GlobalAllowList { for (uint256 i = 0; i < addresses.length; i++) { // If we want to authorize an address, we need to check if the authorization cap has been exceeded // If we want to deauthorize an address, we don't need to check the authorization cap + require(!value, "Cannot deauthorize an address"); require( - !value || - authActions[ritualId] < + authActions[ritualId] < subscription.authorizationActionsCap(ritualId, addresses[i]), "Authorization cap exceeded" ); @@ -118,7 +118,7 @@ contract ManagedAllowList is GlobalAllowList { "Only active rituals can set administrator caps" ); for (uint256 i = 0; i < addresses.length; i++) { - allowance[lookupKey(ritualId, addresses[i])] = value; + allowance[LookupKey.lookupKey(ritualId, addresses[i])] = value; emit AdministratorCapSet(ritualId, addresses[i], value); } authActions[ritualId] += addresses.length; diff --git a/contracts/contracts/coordination/Subscription.sol b/contracts/contracts/coordination/Subscription.sol index 59a418c9b..d39d857c8 100644 --- a/contracts/contracts/coordination/Subscription.sol +++ b/contracts/contracts/coordination/Subscription.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "../lib/LookupKey.sol"; import "./Coordinator.sol"; using SafeERC20 for IERC20; @@ -23,8 +24,8 @@ abstract contract Subscription { address subscriber; } - Coordinator public coordinator; - IERC20 public feeToken; + Coordinator public immutable coordinator; + IERC20 public immutable feeToken; // Mapping from subscription ID to subscription info mapping(uint32 => SubscriptionInfo) public subscriptions; @@ -35,7 +36,8 @@ abstract contract Subscription { uint32 public numberOfSubscriptions; // TODO: DAO Treasury - address public beneficiary; + // TODO: Should it be updatable? + address public immutable beneficiary; /** * @notice Emitted when a subscription is created @@ -73,6 +75,14 @@ abstract contract Subscription { uint256 amount ); + /** + * @notice Emitted when a subscription is spent + * @param beneficiary The address of the beneficiary + * @param amount The amount withdrawn + * @param amount The amount spent + */ + event WithdrawalToBeneficiary(address indexed beneficiary, uint256 amount); + /** * @notice Sets the coordinator and fee token contracts * @dev The coordinator and fee token contracts cannot be zero addresses @@ -105,16 +115,6 @@ abstract contract Subscription { return 52 weeks; // TODO } - /** - * @notice Returns the key used to lookup authorizations - * @param ritualId The ID of the ritual - * @param encryptor The address of the encryptor - * @return The key used to lookup authorizations - */ - function lookupKey(uint32 ritualId, address encryptor) internal pure returns (bytes32) { - return keccak256(abi.encodePacked(ritualId, encryptor)); - } - /** * @notice Creates a new subscription * @param ritualId The ID of the ritual @@ -125,7 +125,7 @@ abstract contract Subscription { sub.subscriber = msg.sender; paySubscriptionFor(subscriptionId); - subscribers[lookupKey(ritualId, msg.sender)] = subscriptionId; + subscribers[LookupKey.lookupKey(ritualId, msg.sender)] = subscriptionId; numberOfSubscriptions += 1; emit SubscriptionCreated(subscriptionId, msg.sender, ritualId); @@ -181,8 +181,9 @@ abstract contract Subscription { function withdrawToBeneficiary(uint256 amount) external { require(msg.sender == beneficiary, "Only the beneficiary can withdraw"); uint256 contractBalance = feeToken.balanceOf(address(this)); - require(contractBalance >= amount, "Insufficient contract balance"); feeToken.safeTransfer(beneficiary, amount); + + emit WithdrawalToBeneficiary(beneficiary, amount); } /** @@ -198,7 +199,7 @@ abstract contract Subscription { uint256 refundAmount = subscriptions[subscriptionId].paidFor; feeToken.safeTransfer(msg.sender, refundAmount); delete subscriptions[subscriptionId]; - delete subscribers[lookupKey(ritualId, msg.sender)]; + delete subscribers[LookupKey.lookupKey(ritualId, msg.sender)]; emit SubscriptionCancelled(subscriptionId, msg.sender, ritualId); } @@ -246,7 +247,7 @@ contract UpfrontSubscriptionWithEncryptorsCap is Subscription { uint32 ritualId, address spender ) public view returns (uint256) { - return authorizationActionCaps[subscribers[lookupKey(ritualId, spender)]]; + return authorizationActionCaps[subscribers[LookupKey.lookupKey(ritualId, spender)]]; } /** diff --git a/contracts/contracts/lib/LookupKey.sol b/contracts/contracts/lib/LookupKey.sol new file mode 100644 index 000000000..72946ffa4 --- /dev/null +++ b/contracts/contracts/lib/LookupKey.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +library LookupKey { + /** + * @notice Returns the key used to lookup authorizations + * @param ritualId The ID of the ritual + * @param encryptor The address of the encryptor + * @return The key used to lookup authorizations + */ + function lookupKey(uint32 ritualId, address encryptor) external pure returns (bytes32) { + return keccak256(abi.encodePacked(ritualId, encryptor)); + } +} diff --git a/tests/test_managed_allow_list.py b/tests/test_managed_allow_list.py index 4a416afb8..7005ea850 100644 --- a/tests/test_managed_allow_list.py +++ b/tests/test_managed_allow_list.py @@ -52,7 +52,11 @@ def subscription(project, coordinator, fee_token, beneficiary, authority): @pytest.fixture() -def brand_new_managed_allow_list(project, coordinator, subscription, authority): +def brand_new_managed_allow_list(project, compilers, coordinator, subscription, deployer, authority): + lookup_key_lib = project.LookupKey.deploy(sender=deployer) + # TODO: Library linking doesn't work + # https://github.com/ApeWorX/ape-solidity#library-linking + compilers.solidity.add_library(lookup_key_lib) return project.ManagedAllowList.deploy( coordinator.address, subscription.address, sender=authority ) diff --git a/tests/test_subscription.py b/tests/test_subscription.py index 58c795664..72f1fd22f 100644 --- a/tests/test_subscription.py +++ b/tests/test_subscription.py @@ -158,6 +158,8 @@ def test_withdraw_to_beneficiary(subscription, fee_token, deployer, subscriber, subscription.withdrawToBeneficiary(subscription_balance, sender=deployer) assert not fee_token.balanceOf(beneficiary) - subscription.withdrawToBeneficiary(subscription_balance, sender=beneficiary) + tx = subscription.withdrawToBeneficiary(subscription_balance, sender=beneficiary) + assert len(tx.events) == 2 + assert tx[1] == subscription.WithdrawalToBeneficiary(beneficiary, subscription_balance) assert fee_token.balanceOf(beneficiary) == subscription_balance assert not fee_token.balanceOf(subscription.address) From 0865dc458321add2a83d1d1d7cc7a15176e5a7cc Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Tue, 23 Apr 2024 14:44:08 +0200 Subject: [PATCH 020/105] apply pr suggestions --- contracts/contracts/coordination/GlobalAllowList.sol | 1 - contracts/contracts/coordination/ManagedAllowList.sol | 3 --- contracts/contracts/coordination/Subscription.sol | 11 ++++++++++- contracts/contracts/lib/LookupKey.sol | 2 +- tests/test_managed_allow_list.py | 8 ++------ tests/test_subscription.py | 2 +- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/contracts/contracts/coordination/GlobalAllowList.sol b/contracts/contracts/coordination/GlobalAllowList.sol index b97fb24a0..732d2f8fa 100644 --- a/contracts/contracts/coordination/GlobalAllowList.sol +++ b/contracts/contracts/coordination/GlobalAllowList.sol @@ -84,7 +84,6 @@ contract GlobalAllowList is IEncryptionAuthorizer { } /** - * @notice Checks if an address is authorized for a ritual * @param ritualId The ID of the ritual * @param evidence The evidence provided * @param ciphertextHeader The header of the ciphertext diff --git a/contracts/contracts/coordination/ManagedAllowList.sol b/contracts/contracts/coordination/ManagedAllowList.sol index 18fc95092..fcd292587 100644 --- a/contracts/contracts/coordination/ManagedAllowList.sol +++ b/contracts/contracts/coordination/ManagedAllowList.sol @@ -90,9 +90,6 @@ contract ManagedAllowList is GlobalAllowList { bool value ) internal view override { for (uint256 i = 0; i < addresses.length; i++) { - // If we want to authorize an address, we need to check if the authorization cap has been exceeded - // If we want to deauthorize an address, we don't need to check the authorization cap - require(!value, "Cannot deauthorize an address"); require( authActions[ritualId] < subscription.authorizationActionsCap(ritualId, addresses[i]), diff --git a/contracts/contracts/coordination/Subscription.sol b/contracts/contracts/coordination/Subscription.sol index d39d857c8..6fe4d4e3f 100644 --- a/contracts/contracts/coordination/Subscription.sol +++ b/contracts/contracts/coordination/Subscription.sol @@ -180,7 +180,16 @@ abstract contract Subscription { */ function withdrawToBeneficiary(uint256 amount) external { require(msg.sender == beneficiary, "Only the beneficiary can withdraw"); - uint256 contractBalance = feeToken.balanceOf(address(this)); + + uint256 availableAmount = 0; + for (uint32 i = 0; i < numberOfSubscriptions; i++) { + SubscriptionInfo storage sub = subscriptions[i]; + if (block.timestamp >= sub.expiration) { + availableAmount += sub.paidFor - sub.spent; + } + } + require(amount <= availableAmount, "Insufficient available amount"); + feeToken.safeTransfer(beneficiary, amount); emit WithdrawalToBeneficiary(beneficiary, amount); diff --git a/contracts/contracts/lib/LookupKey.sol b/contracts/contracts/lib/LookupKey.sol index 72946ffa4..f7a907e72 100644 --- a/contracts/contracts/lib/LookupKey.sol +++ b/contracts/contracts/lib/LookupKey.sol @@ -8,7 +8,7 @@ library LookupKey { * @param encryptor The address of the encryptor * @return The key used to lookup authorizations */ - function lookupKey(uint32 ritualId, address encryptor) external pure returns (bytes32) { + function lookupKey(uint32 ritualId, address encryptor) internal pure returns (bytes32) { return keccak256(abi.encodePacked(ritualId, encryptor)); } } diff --git a/tests/test_managed_allow_list.py b/tests/test_managed_allow_list.py index 7005ea850..3d953c426 100644 --- a/tests/test_managed_allow_list.py +++ b/tests/test_managed_allow_list.py @@ -52,11 +52,7 @@ def subscription(project, coordinator, fee_token, beneficiary, authority): @pytest.fixture() -def brand_new_managed_allow_list(project, compilers, coordinator, subscription, deployer, authority): - lookup_key_lib = project.LookupKey.deploy(sender=deployer) - # TODO: Library linking doesn't work - # https://github.com/ApeWorX/ape-solidity#library-linking - compilers.solidity.add_library(lookup_key_lib) +def brand_new_managed_allow_list(project, coordinator, subscription, deployer, authority): return project.ManagedAllowList.deploy( coordinator.address, subscription.address, sender=authority ) @@ -123,7 +119,7 @@ def test_authorize( # fee_token.Transfer(admin, subscription.address, cost), # managed_allow_list.AddressAuthorizationSet(RITUAL_ID, admin, True) # ] - assert len(tx.events) == 1 + assert len(tx.events) == 3 assert subscription.authorizationActionsCap(RITUAL_ID, admin) == 1000 # Only administrators can authorize encryptors diff --git a/tests/test_subscription.py b/tests/test_subscription.py index 72f1fd22f..3e24065c5 100644 --- a/tests/test_subscription.py +++ b/tests/test_subscription.py @@ -160,6 +160,6 @@ def test_withdraw_to_beneficiary(subscription, fee_token, deployer, subscriber, assert not fee_token.balanceOf(beneficiary) tx = subscription.withdrawToBeneficiary(subscription_balance, sender=beneficiary) assert len(tx.events) == 2 - assert tx[1] == subscription.WithdrawalToBeneficiary(beneficiary, subscription_balance) + assert tx.events[1] == subscription.WithdrawalToBeneficiary(beneficiary, subscription_balance) assert fee_token.balanceOf(beneficiary) == subscription_balance assert not fee_token.balanceOf(subscription.address) From 916e38f2aa1cfd26c3249cb7687abae2419c5e19 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Tue, 23 Apr 2024 18:41:34 +0200 Subject: [PATCH 021/105] apply pr suggestions --- .../contracts/coordination/Subscription.sol | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/contracts/contracts/coordination/Subscription.sol b/contracts/contracts/coordination/Subscription.sol index 6fe4d4e3f..d30221d68 100644 --- a/contracts/contracts/coordination/Subscription.sol +++ b/contracts/contracts/coordination/Subscription.sol @@ -175,12 +175,10 @@ abstract contract Subscription { } /** - * @notice Withdraws the contract balance to the beneficiary - * @param amount The amount to withdraw + * @notice Calculates the available amount of fees that can be withdrawn be the beneficiary + * @return The available amount of fees */ - function withdrawToBeneficiary(uint256 amount) external { - require(msg.sender == beneficiary, "Only the beneficiary can withdraw"); - + function calculateAvailableAmountForBeneficiary() public view returns (uint256) { uint256 availableAmount = 0; for (uint32 i = 0; i < numberOfSubscriptions; i++) { SubscriptionInfo storage sub = subscriptions[i]; @@ -188,10 +186,17 @@ abstract contract Subscription { availableAmount += sub.paidFor - sub.spent; } } - require(amount <= availableAmount, "Insufficient available amount"); + return availableAmount; + } + /** + * @notice Withdraws the contract balance to the beneficiary + * @param amount The amount to withdraw + */ + function withdrawToBeneficiary(uint256 amount) external { + require(msg.sender == beneficiary, "Only the beneficiary can withdraw"); + require(amount <= calculateAvailableAmountForBeneficiary(), "Insufficient available amount"); feeToken.safeTransfer(beneficiary, amount); - emit WithdrawalToBeneficiary(beneficiary, amount); } From 0b216b00fea14858228511fc2942fa6fb0c30d75 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Wed, 15 May 2024 14:10:46 +0200 Subject: [PATCH 022/105] fix solhint failing silently on ci --- .solhint.json | 1 - contracts/contracts/coordination/GlobalAllowList.sol | 4 ++-- contracts/contracts/coordination/ManagedAllowList.sol | 2 ++ contracts/contracts/coordination/Subscription.sol | 5 ++++- contracts/xchain/PolygonChild.sol | 2 +- contracts/xchain/PolygonRoot.sol | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 8 files changed, 15 insertions(+), 11 deletions(-) diff --git a/.solhint.json b/.solhint.json index 0bcafc18a..90dc08e71 100644 --- a/.solhint.json +++ b/.solhint.json @@ -32,7 +32,6 @@ "check-send-result": "error", "func-visibility": ["error", { "ignoreConstructors": true }], "multiple-sends": "error", - "no-complex-fallback": "error", "no-inline-assembly": "off", "no-unused-import": "error", "not-rely-on-block-hash": "error", diff --git a/contracts/contracts/coordination/GlobalAllowList.sol b/contracts/contracts/coordination/GlobalAllowList.sol index 732d2f8fa..bb153eabf 100644 --- a/contracts/contracts/coordination/GlobalAllowList.sol +++ b/contracts/contracts/coordination/GlobalAllowList.sol @@ -22,7 +22,7 @@ contract GlobalAllowList is IEncryptionAuthorizer { mapping(uint32 => uint256) public authActions; - uint32 public MAX_AUTH_ACTIONS = 100; + uint32 public maxAuthActions = 100; /** * @notice Emitted when an address authorization is set @@ -149,7 +149,7 @@ contract GlobalAllowList is IEncryptionAuthorizer { function setAuthorizations(uint32 ritualId, address[] calldata addresses, bool value) internal { require(coordinator.isRitualActive(ritualId), "Only active rituals can set authorizations"); - require(addresses.length <= MAX_AUTH_ACTIONS, "Too many addresses"); + require(addresses.length <= maxAuthActions, "Too many addresses"); _beforeSetAuthorization(ritualId, addresses, value); for (uint256 i = 0; i < addresses.length; i++) { diff --git a/contracts/contracts/coordination/ManagedAllowList.sol b/contracts/contracts/coordination/ManagedAllowList.sol index fcd292587..f548a7105 100644 --- a/contracts/contracts/coordination/ManagedAllowList.sol +++ b/contracts/contracts/coordination/ManagedAllowList.sol @@ -87,6 +87,8 @@ contract ManagedAllowList is GlobalAllowList { function _beforeSetAuthorization( uint32 ritualId, address[] calldata addresses, + // TODO: Currently unused, remove? + // solhint-disable-next-line no-unused-vars bool value ) internal view override { for (uint256 i = 0; i < addresses.length; i++) { diff --git a/contracts/contracts/coordination/Subscription.sol b/contracts/contracts/coordination/Subscription.sol index d30221d68..e472702e9 100644 --- a/contracts/contracts/coordination/Subscription.sol +++ b/contracts/contracts/coordination/Subscription.sol @@ -195,7 +195,10 @@ abstract contract Subscription { */ function withdrawToBeneficiary(uint256 amount) external { require(msg.sender == beneficiary, "Only the beneficiary can withdraw"); - require(amount <= calculateAvailableAmountForBeneficiary(), "Insufficient available amount"); + require( + amount <= calculateAvailableAmountForBeneficiary(), + "Insufficient available amount" + ); feeToken.safeTransfer(beneficiary, amount); emit WithdrawalToBeneficiary(beneficiary, amount); } diff --git a/contracts/xchain/PolygonChild.sol b/contracts/xchain/PolygonChild.sol index a890613d5..f532168a3 100644 --- a/contracts/xchain/PolygonChild.sol +++ b/contracts/xchain/PolygonChild.sol @@ -35,7 +35,7 @@ contract PolygonChild is FxBaseChildTunnel, Ownable { } } - fallback() external { + fallback() external payable { require(msg.sender == childApplication, "Only child app can call this method"); _sendMessageToRoot(msg.data); } diff --git a/contracts/xchain/PolygonRoot.sol b/contracts/xchain/PolygonRoot.sol index 48d39c5ec..994ecb90e 100644 --- a/contracts/xchain/PolygonRoot.sol +++ b/contracts/xchain/PolygonRoot.sol @@ -27,7 +27,7 @@ contract PolygonRoot is FxBaseRootTunnel { require(success, "Root tx failed"); } - fallback() external { + fallback() external payable { require(msg.sender == rootApplication, "Caller must be the root app"); _sendMessageToChild(msg.data); } diff --git a/package-lock.json b/package-lock.json index d8369fd10..b82b4e210 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "ganache": "^7.9.1", "prettier": "^2.8.8", "prettier-plugin-solidity": "^1.1.3", - "solhint": "^4.0.0", + "solhint": "^5.0.1", "solhint-plugin-prettier": "^0.0.5", "typescript": "^5.2.2", "vitest": "^0.34.6" @@ -8532,9 +8532,9 @@ } }, "node_modules/solhint": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/solhint/-/solhint-4.5.4.tgz", - "integrity": "sha512-Cu1XiJXub2q1eCr9kkJ9VPv1sGcmj3V7Zb76B0CoezDOB9bu3DxKIFFH7ggCl9fWpEPD6xBmRLfZrYijkVmujQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/solhint/-/solhint-5.0.1.tgz", + "integrity": "sha512-QeQLS9HGCnIiibt+xiOa/+MuP7BWz9N7C5+Mj9pLHshdkNhuo3AzCpWmjfWVZBUuwIUO3YyCRVIcYLR3YOKGfg==", "dev": true, "dependencies": { "@solidity-parser/parser": "^0.18.0", diff --git a/package.json b/package.json index 00b5b6588..45469109c 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "ganache": "^7.9.1", "prettier": "^2.8.8", "prettier-plugin-solidity": "^1.1.3", - "solhint": "^4.0.0", + "solhint": "^5.0.1", "solhint-plugin-prettier": "^0.0.5", "typescript": "^5.2.2", "vitest": "^0.34.6" From 0db349a4e24d7c5ac90ed99f3534c2d6e33fabd5 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Wed, 15 May 2024 16:59:05 +0200 Subject: [PATCH 023/105] remove solidity oopsies --- contracts/contracts/coordination/GlobalAllowList.sol | 4 ++-- contracts/xchain/PolygonChild.sol | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/contracts/coordination/GlobalAllowList.sol b/contracts/contracts/coordination/GlobalAllowList.sol index bb153eabf..f3bb52fa2 100644 --- a/contracts/contracts/coordination/GlobalAllowList.sol +++ b/contracts/contracts/coordination/GlobalAllowList.sol @@ -22,7 +22,7 @@ contract GlobalAllowList is IEncryptionAuthorizer { mapping(uint32 => uint256) public authActions; - uint32 public maxAuthActions = 100; + uint32 public constant MAX_AUTH_ACTIONS = 100; /** * @notice Emitted when an address authorization is set @@ -149,7 +149,7 @@ contract GlobalAllowList is IEncryptionAuthorizer { function setAuthorizations(uint32 ritualId, address[] calldata addresses, bool value) internal { require(coordinator.isRitualActive(ritualId), "Only active rituals can set authorizations"); - require(addresses.length <= maxAuthActions, "Too many addresses"); + require(addresses.length <= MAX_AUTH_ACTIONS, "Too many addresses"); _beforeSetAuthorization(ritualId, addresses, value); for (uint256 i = 0; i < addresses.length; i++) { diff --git a/contracts/xchain/PolygonChild.sol b/contracts/xchain/PolygonChild.sol index f532168a3..a890613d5 100644 --- a/contracts/xchain/PolygonChild.sol +++ b/contracts/xchain/PolygonChild.sol @@ -35,7 +35,7 @@ contract PolygonChild is FxBaseChildTunnel, Ownable { } } - fallback() external payable { + fallback() external { require(msg.sender == childApplication, "Only child app can call this method"); _sendMessageToRoot(msg.data); } From 2a149543cda86e087f9a8ac90143fbf4ecc2f669 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Tue, 14 May 2024 20:33:12 -0400 Subject: [PATCH 024/105] Extracting fee model from Coordinator --- .../coordination/BetaProgramInitiator.sol | 30 +- .../contracts/coordination/Coordinator.sol | 114 ++---- .../coordination/FlatRateFeeModel.sol | 76 +++- .../contracts/coordination/IFeeModel.sol | 9 +- .../test/BetaProgramInitiatorTestSet.sol | 58 +-- tests/test_beta_program_initiator.py | 145 ++++---- tests/test_coordinator.py | 331 +++++++++--------- 7 files changed, 369 insertions(+), 394 deletions(-) diff --git a/contracts/contracts/coordination/BetaProgramInitiator.sol b/contracts/contracts/coordination/BetaProgramInitiator.sol index 3b21fe96b..ec7b72c1e 100644 --- a/contracts/contracts/coordination/BetaProgramInitiator.sol +++ b/contracts/contracts/coordination/BetaProgramInitiator.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "./FlatRateFeeModel.sol"; import "./Coordinator.sol"; @@ -16,7 +17,6 @@ contract BetaProgramInitiator { address[] providers, address authority, uint32 duration, - IEncryptionAuthorizer accessController, uint256 payment ); @@ -30,7 +30,6 @@ contract BetaProgramInitiator { address[] providers; address authority; uint32 duration; - IEncryptionAuthorizer accessController; address sender; uint32 ritualId; uint256 payment; @@ -41,14 +40,16 @@ contract BetaProgramInitiator { Coordinator public immutable coordinator; IERC20 public immutable currency; address public immutable executor; // TODO transferable role? + FlatRateFeeModel public immutable feeModel; InitiationRequest[] public requests; - constructor(Coordinator _coordinator, address _executor) { + constructor(Coordinator _coordinator, address _executor, FlatRateFeeModel _feeModel) { require(_executor != address(0), "Invalid parameters"); coordinator = _coordinator; - currency = coordinator.currency(); + currency = _feeModel.currency(); executor = _executor; + feeModel = _feeModel; } function getRequestsLength() external view returns (uint256) { @@ -63,17 +64,15 @@ contract BetaProgramInitiator { function registerInitiationRequest( address[] calldata providers, address authority, - uint32 duration, - IEncryptionAuthorizer accessController + uint32 duration ) external returns (uint256 requestIndex) { - uint256 ritualCost = coordinator.getRitualInitiationCost(providers, duration); + uint256 ritualCost = feeModel.getRitualInitiationCost(providers.length, duration); requestIndex = requests.length; InitiationRequest storage request = requests.push(); request.providers = providers; request.authority = authority; request.duration = duration; - request.accessController = accessController; request.sender = msg.sender; request.ritualId = NO_RITUAL; request.payment = ritualCost; @@ -84,7 +83,6 @@ contract BetaProgramInitiator { providers, authority, duration, - accessController, ritualCost ); currency.safeTransferFrom(msg.sender, address(this), ritualCost); @@ -119,15 +117,15 @@ contract BetaProgramInitiator { address[] memory providers = request.providers; uint32 duration = request.duration; - uint256 ritualCost = coordinator.getRitualInitiationCost(providers, duration); + uint256 ritualCost = feeModel.getRitualInitiationCost(providers.length, duration); require(ritualCost == request.payment, "Ritual initiation cost has changed"); - currency.approve(address(coordinator), ritualCost); + currency.approve(address(feeModel), ritualCost); uint32 ritualId = coordinator.initiateRitual( + feeModel, providers, request.authority, - duration, - request.accessController + duration ); request.ritualId = ritualId; emit RequestExecuted(requestIndex, ritualId); @@ -149,9 +147,9 @@ contract BetaProgramInitiator { // Process pending fees in Coordinator, if necessary uint256 refundAmount = request.payment; - refundAmount -= coordinator.feeDeduction(request.payment, request.duration); - if (coordinator.pendingFees(ritualId) > 0) { - coordinator.processPendingFee(ritualId); + refundAmount -= feeModel.feeDeduction(request.payment, request.duration); + if (feeModel.pendingFees(ritualId) > 0) { + feeModel.processPendingFee(ritualId); } // Erase payment and transfer refund to original sender diff --git a/contracts/contracts/coordination/Coordinator.sol b/contracts/contracts/coordination/Coordinator.sol index c23f591e7..e728e1750 100644 --- a/contracts/contracts/coordination/Coordinator.sol +++ b/contracts/contracts/coordination/Coordinator.sol @@ -4,19 +4,16 @@ pragma solidity ^0.8.0; import "@openzeppelin-upgradeable/contracts/access/extensions/AccessControlDefaultAdminRulesUpgradeable.sol"; import "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "./FlatRateFeeModel.sol"; +import "./IFeeModel.sol"; import "./IReimbursementPool.sol"; import "../lib/BLS12381.sol"; import "../../threshold/ITACoChildApplication.sol"; -import "./IEncryptionAuthorizer.sol"; /** * @title Coordinator * @notice Coordination layer for Threshold Access Control (TACo 🌮) */ -contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable, FlatRateFeeModel { +contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable { // DKG Protocol event StartRitual(uint32 indexed ritualId, address indexed authority, address[] participants); event StartAggregationRound(uint32 indexed ritualId); @@ -44,6 +41,7 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable address indexed participant, BLS12381.G2Point publicKey ); + event FeeModelApproved(IFeeModel feeModel); enum RitualState { NON_INITIATED, @@ -76,7 +74,7 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable uint16 threshold; bool aggregationMismatch; // - IEncryptionAuthorizer accessController; + address stub1; // former accessController BLS12381.G1Point publicKey; bytes aggregatedTranscript; Participant[] participant; @@ -87,38 +85,35 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable BLS12381.G2Point publicKey; } - using SafeERC20 for IERC20; - bytes32 public constant INITIATOR_ROLE = keccak256("INITIATOR_ROLE"); bytes32 public constant TREASURY_ROLE = keccak256("TREASURY_ROLE"); ITACoChildApplication public immutable application; - uint96 private immutable minAuthorization; + uint96 private immutable minAuthorization; // todo remove Ritual[] public rituals; uint32 public timeout; uint16 public maxDkgSize; bool public isInitiationPublic; - uint256 public totalPendingFees; - mapping(uint256 => uint256) public pendingFees; - IFeeModel internal feeModel; // TODO: Consider making feeModel specific to each ritual + + uint256 public stub1; // former totalPendingFees + mapping(uint256 => uint256) private stub2; // former pendingFees + address private stub3; // former feeModel + IReimbursementPool internal reimbursementPool; mapping(address => ParticipantKey[]) internal participantKeysHistory; mapping(bytes32 => uint32) internal ritualPublicKeyRegistry; + mapping(IFeeModel => bool) public feeModelsRegistry; // Note: Adjust the __preSentinelGap size if more contract variables are added // Storage area for sentinel values - uint256[20] internal __preSentinelGap; + uint256[19] internal __preSentinelGap; Participant internal __sentinelParticipant; uint256[20] internal __postSentinelGap; - constructor( - ITACoChildApplication _application, - IERC20 _currency, - uint256 _feeRatePerSecond - ) FlatRateFeeModel(_currency, _feeRatePerSecond) { + constructor(ITACoChildApplication _application) { application = _application; - minAuthorization = _application.minimumAuthorization(); + minAuthorization = _application.minimumAuthorization(); // TODO remove } /** @@ -130,6 +125,17 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable __AccessControlDefaultAdminRules_init(0, _admin); } + function getInitiator(uint32 ritualId) external view returns (address) { + return rituals[ritualId].initiator; + } + + function getTimestamps( + uint32 ritualId + ) external view returns (uint32 initTimestamp, uint32 endTimestamp) { + initTimestamp = rituals[ritualId].initTimestamp; + endTimestamp = rituals[ritualId].endTimestamp; + } + function getRitualState(uint32 ritualId) external view returns (RitualState) { return getRitualState(rituals[ritualId]); } @@ -258,10 +264,10 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable } function initiateRitual( + IFeeModel feeModel, address[] calldata providers, address authority, - uint32 duration, - IEncryptionAuthorizer accessController + uint32 duration ) external returns (uint32) { require(authority != address(0), "Invalid authority"); @@ -269,6 +275,7 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable isInitiationPublic || hasRole(INITIATOR_ROLE, msg.sender), "Sender can't initiate ritual" ); + require(feeModelsRegistry[feeModel], "Fee model must be approved"); uint16 length = uint16(providers.length); require(2 <= length && length <= maxDkgSize, "Invalid number of nodes"); require(duration >= 24 hours, "Invalid ritual duration"); // TODO: Define minimum duration #106 @@ -281,7 +288,6 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable ritual.threshold = getThresholdForRitualSize(length); ritual.initTimestamp = uint32(block.timestamp); ritual.endTimestamp = ritual.initTimestamp + duration; - ritual.accessController = accessController; address previous = address(0); for (uint256 i = 0; i < length; i++) { @@ -302,7 +308,7 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable previous = current; } - processRitualPayment(id, providers, duration); + feeModel.processRitualPayment(msg.sender, id, length, duration); emit StartRitual(id, ritual.authority, providers); return id; @@ -397,7 +403,7 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable if (!ritual.aggregationMismatch) { ritual.totalAggregations++; if (ritual.totalAggregations == ritual.dkgSize) { - processPendingFee(ritualId); + // processPendingFee(ritualId); TODO consider to notify feeModel // Register ritualId + 1 to discern ritualID#0 from unregistered keys. // See getRitualIdFromPublicKey() for inverse operation. bytes32 registryKey = keccak256( @@ -532,54 +538,6 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable return found; } - function isEncryptionAuthorized( - uint32 ritualId, - bytes memory evidence, - bytes memory ciphertextHeader - ) external view returns (bool) { - Ritual storage ritual = rituals[ritualId]; - require(getRitualState(ritual) == RitualState.ACTIVE, "Ritual not active"); - return ritual.accessController.isAuthorized(ritualId, evidence, ciphertextHeader); - } - - function processRitualPayment( - uint32 ritualId, - address[] calldata providers, - uint32 duration - ) internal { - uint256 ritualCost = getRitualInitiationCost(providers, duration); - require(ritualCost > 0, "Invalid ritual cost"); - totalPendingFees += ritualCost; - pendingFees[ritualId] = ritualCost; - currency.safeTransferFrom(msg.sender, address(this), ritualCost); - } - - function processPendingFee(uint32 ritualId) public returns (uint256 refundableFee) { - Ritual storage ritual = rituals[ritualId]; - RitualState state = getRitualState(ritual); - require( - state == RitualState.DKG_TIMEOUT || - state == RitualState.DKG_INVALID || - state == RitualState.ACTIVE || - state == RitualState.EXPIRED, - "Ritual is not ended" - ); - uint256 pending = pendingFees[ritualId]; - require(pending > 0, "No pending fees for this ritual"); - - // Finalize fees for this ritual - totalPendingFees -= pending; - delete pendingFees[ritualId]; - // Transfer fees back to initiator if failed - if (state == RitualState.DKG_TIMEOUT || state == RitualState.DKG_INVALID) { - // Refund everything minus cost of renting cohort for a day - uint256 duration = ritual.endTimestamp - ritual.initTimestamp; - refundableFee = pending - feeDeduction(pending, duration); - currency.safeTransfer(ritual.initiator, refundableFee); - } - return refundableFee; - } - function processReimbursement(uint256 initialGasLeft) internal { if (address(reimbursementPool) != address(0)) { uint256 gasUsed = initialGasLeft - gasleft(); @@ -591,13 +549,9 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable } } - function withdrawTokens(IERC20 token, uint256 amount) external onlyRole(TREASURY_ROLE) { - if (address(token) == address(currency)) { - require( - amount <= token.balanceOf(address(this)) - totalPendingFees, - "Can't withdraw pending fees" - ); - } - token.safeTransfer(msg.sender, amount); + function approveFeeModel(IFeeModel feeModel) external onlyRole(TREASURY_ROLE) { + require(!feeModelsRegistry[feeModel], "Fee model already approved"); + feeModelsRegistry[feeModel] = true; + emit FeeModelApproved(feeModel); } } diff --git a/contracts/contracts/coordination/FlatRateFeeModel.sol b/contracts/contracts/coordination/FlatRateFeeModel.sol index 45620f648..08ec2107f 100644 --- a/contracts/contracts/coordination/FlatRateFeeModel.sol +++ b/contracts/contracts/coordination/FlatRateFeeModel.sol @@ -3,34 +3,98 @@ pragma solidity ^0.8.0; import "./IFeeModel.sol"; +import "./Coordinator.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; /** * @title FlatRateFeeModel * @notice FlatRateFeeModel */ -contract FlatRateFeeModel is IFeeModel { +contract FlatRateFeeModel is IFeeModel, Ownable { + using SafeERC20 for IERC20; + IERC20 public immutable currency; uint256 public immutable feeRatePerSecond; + Coordinator public immutable coordinator; + + uint256 public totalPendingFees; + mapping(uint256 => uint256) public pendingFees; - constructor(IERC20 _currency, uint256 _feeRatePerSecond) { + constructor( + Coordinator _coordinator, + IERC20 _currency, + uint256 _feeRatePerSecond + ) Ownable(msg.sender) { require(_feeRatePerSecond > 0, "Invalid fee rate"); currency = _currency; feeRatePerSecond = _feeRatePerSecond; + coordinator = _coordinator; } function getRitualInitiationCost( - address[] calldata providers, + uint256 numberOfProviders, uint32 duration ) public view returns (uint256) { - uint256 size = providers.length; require(duration > 0, "Invalid ritual duration"); - require(size > 0, "Invalid ritual size"); - return feeRatePerSecond * size * duration; + require(numberOfProviders > 0, "Invalid ritual size"); + return feeRatePerSecond * numberOfProviders * duration; } // TODO: Validate if this is enough to remove griefing attacks function feeDeduction(uint256, uint256) public pure returns (uint256) { return 0; } + + function processRitualPayment( + address initiator, + uint32 ritualId, + uint256 numberOfProviders, + uint32 duration + ) external override { + uint256 ritualCost = getRitualInitiationCost(numberOfProviders, duration); + require(ritualCost > 0, "Invalid ritual cost"); + totalPendingFees += ritualCost; + pendingFees[ritualId] = ritualCost; + currency.safeTransferFrom(initiator, address(this), ritualCost); + } + + function processPendingFee(uint32 ritualId) public returns (uint256 refundableFee) { + Coordinator.RitualState state = coordinator.getRitualState(ritualId); + require( + state == Coordinator.RitualState.DKG_TIMEOUT || + state == Coordinator.RitualState.DKG_INVALID || + state == Coordinator.RitualState.ACTIVE || + state == Coordinator.RitualState.EXPIRED, + "Ritual is not ended" + ); + uint256 pending = pendingFees[ritualId]; + require(pending > 0, "No pending fees for this ritual"); + + // Finalize fees for this ritual + totalPendingFees -= pending; + delete pendingFees[ritualId]; + // Transfer fees back to initiator if failed + if ( + state == Coordinator.RitualState.DKG_TIMEOUT || + state == Coordinator.RitualState.DKG_INVALID + ) { + // Refund everything minus cost of renting cohort for a day + address initiator = coordinator.getInitiator(ritualId); + (uint32 initTimestamp, uint32 endTimestamp) = coordinator.getTimestamps(ritualId); + uint256 duration = endTimestamp - initTimestamp; + refundableFee = pending - feeDeduction(pending, duration); + currency.safeTransfer(initiator, refundableFee); + } + return refundableFee; + } + + function withdrawTokens(uint256 amount) external onlyOwner { + require( + amount <= currency.balanceOf(address(this)) - totalPendingFees, + "Can't withdraw pending fees" + ); + currency.safeTransfer(msg.sender, amount); + } } diff --git a/contracts/contracts/coordination/IFeeModel.sol b/contracts/contracts/coordination/IFeeModel.sol index c53b4d2f6..4e9fe5ade 100644 --- a/contracts/contracts/coordination/IFeeModel.sol +++ b/contracts/contracts/coordination/IFeeModel.sol @@ -12,7 +12,14 @@ interface IFeeModel { function currency() external view returns (IERC20); function getRitualInitiationCost( - address[] calldata providers, + uint256 numberOfProviders, uint32 duration ) external view returns (uint256); + + function processRitualPayment( + address initiator, + uint32 ritualId, + uint256 numberOfProviders, + uint32 duration + ) external; } diff --git a/contracts/test/BetaProgramInitiatorTestSet.sol b/contracts/test/BetaProgramInitiatorTestSet.sol index 0de777fec..6b38c213b 100644 --- a/contracts/test/BetaProgramInitiatorTestSet.sol +++ b/contracts/test/BetaProgramInitiatorTestSet.sol @@ -6,6 +6,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "../contracts/coordination/IEncryptionAuthorizer.sol"; import "../contracts/coordination/Coordinator.sol"; +import "../contracts/coordination/IFeeModel.sol"; contract CoordinatorForBetaProgramInitiatorMock { using SafeERC20 for IERC20; @@ -15,35 +16,17 @@ contract CoordinatorForBetaProgramInitiatorMock { address[] providers; address authority; uint32 duration; - IEncryptionAuthorizer accessController; Coordinator.RitualState state; uint256 ritualCost; + IFeeModel feeModel; } - IERC20 public immutable currency; - - uint256 public feeRatePerSecond = 10 ** 18; Ritual[] public rituals; - constructor(IERC20 _currency) { - currency = _currency; - } - - function setFeeRatePerSecond(uint256 _feeRatePerSecond) external { - feeRatePerSecond = _feeRatePerSecond; - } - function getRitualsLength() external view returns (uint256) { return rituals.length; } - function getRitualInitiationCost( - address[] calldata _providers, - uint32 _duration - ) public view returns (uint256) { - return feeRatePerSecond * _providers.length * _duration; - } - function getRitualState(uint32 _ritualId) external view returns (Coordinator.RitualState) { if (_ritualId > rituals.length) { return Coordinator.RitualState.NON_INITIATED; @@ -61,44 +44,33 @@ contract CoordinatorForBetaProgramInitiatorMock { } function initiateRitual( + IFeeModel _feeModel, address[] calldata _providers, address _authority, - uint32 _duration, - IEncryptionAuthorizer accessController + uint32 _duration ) external returns (uint32 ritualId) { Ritual storage ritual = rituals.push(); ritual.initiator = msg.sender; ritual.providers = _providers; ritual.authority = _authority; ritual.duration = _duration; - ritual.accessController = accessController; + ritual.feeModel = _feeModel; ritual.state = Coordinator.RitualState.DKG_AWAITING_TRANSCRIPTS; - ritual.ritualCost = getRitualInitiationCost(_providers, _duration); - currency.safeTransferFrom(msg.sender, address(this), ritual.ritualCost); + uint32 id = uint32(rituals.length - 1); + _feeModel.processRitualPayment(msg.sender, id, _providers.length, _duration); - return uint32(rituals.length - 1); + return id; } - function feeDeduction(uint256 pending, uint256) public pure returns (uint256) { - return pending / 10; + function getInitiator(uint32 ritualId) external view returns (address) { + return rituals[ritualId].initiator; } - function pendingFees(uint256 _ritualId) external view returns (uint256) { - return rituals[_ritualId].ritualCost; - } - - function processPendingFee(uint32 _ritualId) public returns (uint256) { - Ritual storage ritual = rituals[_ritualId]; - uint256 refundableFee = 0; - if ( - ritual.state == Coordinator.RitualState.DKG_TIMEOUT || - ritual.state == Coordinator.RitualState.DKG_INVALID - ) { - refundableFee = ritual.ritualCost - feeDeduction(ritual.ritualCost, 0); - currency.safeTransfer(ritual.initiator, refundableFee); - } - ritual.ritualCost = 0; - return refundableFee; + function getTimestamps( + uint32 ritualId + ) external view returns (uint32 initTimestamp, uint32 endTimestamp) { + initTimestamp = uint32(block.timestamp); + endTimestamp = uint32(block.timestamp + rituals[ritualId].duration); } } diff --git a/tests/test_beta_program_initiator.py b/tests/test_beta_program_initiator.py index c817e41c1..e1b28d80c 100644 --- a/tests/test_beta_program_initiator.py +++ b/tests/test_beta_program_initiator.py @@ -23,10 +23,10 @@ AUTHORITY_SLOT = 0 DURATION_SLOT = 1 -ACCESS_CONTROLLER_SLOT = 2 -SENDER_SLOT = 3 -RITUAL_ID_SLOT = 4 -PAYMENT_SLOT = 5 +SENDER_SLOT = 2 +RITUAL_ID_SLOT = 3 +PAYMENT_SLOT = 4 +FEE_RATE = 42 RitualState = IntEnum( "RitualState", @@ -55,51 +55,59 @@ def token(project, creator): @pytest.fixture() -def coordinator(project, token, creator): - contract = project.CoordinatorForBetaProgramInitiatorMock.deploy(token.address, sender=creator) +def coordinator(project, creator): + contract = project.CoordinatorForBetaProgramInitiatorMock.deploy(sender=creator) return contract @pytest.fixture() -def beta_program_initiator(project, coordinator, executor, creator): - contract = project.BetaProgramInitiator.deploy(coordinator.address, executor, sender=creator) +def fee_model(project, creator, coordinator, token): + contract = project.FlatRateFeeModel.deploy( + coordinator.address, token.address, FEE_RATE, sender=creator + ) return contract -def test_register(accounts, beta_program_initiator, token, coordinator): +@pytest.fixture() +def beta_program_initiator(project, coordinator, executor, creator, fee_model): + contract = project.BetaProgramInitiator.deploy( + coordinator.address, executor, fee_model.address, sender=creator + ) + return contract + + +def test_register(accounts, beta_program_initiator, token, fee_model): ( initiator_1, initiator_2, authority, node_1, node_2, - access_controller, *everyone_else, ) = accounts[2:] no_ritual = beta_program_initiator.NO_RITUAL() nodes = [node_1, node_2] duration = DAY_IN_SECONDS - ritual_cost = coordinator.getRitualInitiationCost(nodes, duration) + ritual_cost = fee_model.getRitualInitiationCost(len(nodes), duration) # Can't register request without token transfer approval with ape.reverts(): beta_program_initiator.registerInitiationRequest( - nodes, authority, duration, access_controller, sender=initiator_1 + nodes, authority, duration, sender=initiator_1 ) # Register request token.mint(initiator_1, 10 * ritual_cost, sender=initiator_1) token.approve(beta_program_initiator.address, 10 * ritual_cost, sender=initiator_1) tx = beta_program_initiator.registerInitiationRequest( - nodes, authority, duration, access_controller, sender=initiator_1 + nodes, authority, duration, sender=initiator_1 ) assert beta_program_initiator.getRequestsLength() == 1 assert beta_program_initiator.getProviders(0) == nodes request = beta_program_initiator.requests(0) assert request[AUTHORITY_SLOT] == authority assert request[DURATION_SLOT] == duration - assert request[ACCESS_CONTROLLER_SLOT] == access_controller assert request[SENDER_SLOT] == initiator_1 assert request[RITUAL_ID_SLOT] == no_ritual assert request[PAYMENT_SLOT] == ritual_cost @@ -113,25 +121,23 @@ def test_register(accounts, beta_program_initiator, token, coordinator): assert event.providers == [n.address for n in nodes] assert event.authority == authority assert event.duration == duration - assert event.accessController == access_controller assert event.payment == ritual_cost # Register another request nodes = [node_1] duration = 3 * DAY_IN_SECONDS - ritual_cost_2 = coordinator.getRitualInitiationCost(nodes, duration) + ritual_cost_2 = fee_model.getRitualInitiationCost(len(nodes), duration) token.mint(initiator_2, ritual_cost_2, sender=initiator_2) token.approve(beta_program_initiator.address, ritual_cost_2, sender=initiator_2) tx = beta_program_initiator.registerInitiationRequest( - nodes, authority, duration, access_controller, sender=initiator_2 + nodes, authority, duration, sender=initiator_2 ) assert beta_program_initiator.getRequestsLength() == 2 assert beta_program_initiator.getProviders(1) == nodes request = beta_program_initiator.requests(1) assert request[AUTHORITY_SLOT] == authority assert request[DURATION_SLOT] == duration - assert request[ACCESS_CONTROLLER_SLOT] == access_controller assert request[SENDER_SLOT] == initiator_2 assert request[RITUAL_ID_SLOT] == no_ritual assert request[PAYMENT_SLOT] == ritual_cost_2 @@ -145,24 +151,22 @@ def test_register(accounts, beta_program_initiator, token, coordinator): assert event.providers == [n.address for n in nodes] assert event.authority == authority assert event.duration == duration - assert event.accessController == access_controller assert event.payment == ritual_cost_2 -def test_cancel(accounts, beta_program_initiator, token, coordinator, executor): +def test_cancel(accounts, beta_program_initiator, token, executor, fee_model): ( initiator_1, initiator_2, authority, node_1, node_2, - access_controller, *everyone_else, ) = accounts[2:] nodes = [node_1, node_2] duration = DAY_IN_SECONDS - ritual_cost = coordinator.getRitualInitiationCost(nodes, duration) + ritual_cost = fee_model.getRitualInitiationCost(len(nodes), duration) token.mint(initiator_1, 10 * ritual_cost, sender=initiator_1) token.approve(beta_program_initiator.address, 10 * ritual_cost, sender=initiator_1) @@ -174,15 +178,9 @@ def test_cancel(accounts, beta_program_initiator, token, coordinator, executor): beta_program_initiator.cancelInitiationRequest(0, sender=executor) # Register three requests - beta_program_initiator.registerInitiationRequest( - nodes, authority, duration, access_controller, sender=initiator_1 - ) - beta_program_initiator.registerInitiationRequest( - nodes, authority, duration, access_controller, sender=initiator_1 - ) - beta_program_initiator.registerInitiationRequest( - nodes, authority, duration, access_controller, sender=initiator_2 - ) + beta_program_initiator.registerInitiationRequest(nodes, authority, duration, sender=initiator_1) + beta_program_initiator.registerInitiationRequest(nodes, authority, duration, sender=initiator_1) + beta_program_initiator.registerInitiationRequest(nodes, authority, duration, sender=initiator_2) # Only initiator or executor can cancel request with ape.reverts("Not allowed to cancel"): @@ -222,7 +220,7 @@ def test_cancel(accounts, beta_program_initiator, token, coordinator, executor): beta_program_initiator.cancelInitiationRequest(1, sender=executor) -def test_execute(accounts, beta_program_initiator, token, coordinator, executor): +def test_execute(accounts, beta_program_initiator, token, coordinator, executor, fee_model): ( initiator_1, initiator_2, @@ -230,18 +228,16 @@ def test_execute(accounts, beta_program_initiator, token, coordinator, executor) authority_2, node_1, node_2, - access_controller_1, - access_controller_2, *everyone_else, ) = accounts[2:] no_ritual = beta_program_initiator.NO_RITUAL() nodes_1 = [node_1, node_2] duration_1 = DAY_IN_SECONDS - ritual_cost_1 = coordinator.getRitualInitiationCost(nodes_1, duration_1) + ritual_cost_1 = fee_model.getRitualInitiationCost(len(nodes_1), duration_1) nodes_2 = [node_1] duration_2 = 2 * duration_1 - ritual_cost_2 = coordinator.getRitualInitiationCost(nodes_2, duration_2) + ritual_cost_2 = fee_model.getRitualInitiationCost(len(nodes_2), duration_2) token.mint(initiator_1, 10 * ritual_cost_1, sender=initiator_1) token.approve(beta_program_initiator.address, 10 * ritual_cost_1, sender=initiator_1) @@ -254,13 +250,13 @@ def test_execute(accounts, beta_program_initiator, token, coordinator, executor) # Register three requests beta_program_initiator.registerInitiationRequest( - nodes_1, authority_1, duration_1, access_controller_1, sender=initiator_1 + nodes_1, authority_1, duration_1, sender=initiator_1 ) beta_program_initiator.registerInitiationRequest( - nodes_2, authority_2, duration_2, access_controller_2, sender=initiator_2 + nodes_2, authority_2, duration_2, sender=initiator_2 ) beta_program_initiator.registerInitiationRequest( - nodes_2, authority_1, duration_1, access_controller_2, sender=initiator_1 + nodes_2, authority_1, duration_1, sender=initiator_1 ) # Only executor can execute request @@ -279,7 +275,7 @@ def test_execute(accounts, beta_program_initiator, token, coordinator, executor) assert beta_program_initiator.requests(0)[RITUAL_ID_SLOT] == no_ritual balance_after = token.balanceOf(beta_program_initiator.address) assert balance_before - balance_after == ritual_cost_2 - assert token.balanceOf(coordinator.address) == ritual_cost_2 + assert token.balanceOf(fee_model.address) == ritual_cost_2 assert coordinator.getRitualsLength() == 1 assert coordinator.getProviders(0) == nodes_2 @@ -287,9 +283,9 @@ def test_execute(accounts, beta_program_initiator, token, coordinator, executor) assert ritual[0] == beta_program_initiator.address assert ritual[1] == authority_2 assert ritual[2] == duration_2 - assert ritual[3] == access_controller_2 - assert ritual[4] == 1 - assert ritual[5] == ritual_cost_2 + assert ritual[3] == 1 + # assert ritual[4] == ritual_cost_2 + assert ritual[5] == fee_model.address events = beta_program_initiator.RequestExecuted.from_receipt(tx) assert events == [beta_program_initiator.RequestExecuted(1, 0)] @@ -299,23 +295,23 @@ def test_execute(accounts, beta_program_initiator, token, coordinator, executor) beta_program_initiator.executeInitiationRequest(1, sender=executor) # Can't execute request if ritual cost changes - fee_rate = coordinator.feeRatePerSecond() - coordinator.setFeeRatePerSecond(2 * fee_rate, sender=executor) - with ape.reverts("Ritual initiation cost has changed"): - beta_program_initiator.executeInitiationRequest(0, sender=executor) - coordinator.setFeeRatePerSecond(fee_rate // 2, sender=executor) - with ape.reverts("Ritual initiation cost has changed"): - beta_program_initiator.executeInitiationRequest(0, sender=executor) + # fee_rate = fee_model.feeRatePerSecond() + # coordinator.setFeeRatePerSecond(2 * fee_rate, sender=executor) + # with ape.reverts("Ritual initiation cost has changed"): + # beta_program_initiator.executeInitiationRequest(0, sender=executor) + # coordinator.setFeeRatePerSecond(fee_rate // 2, sender=executor) + # with ape.reverts("Ritual initiation cost has changed"): + # beta_program_initiator.executeInitiationRequest(0, sender=executor) # Return fee rate back and execute request again - coordinator.setFeeRatePerSecond(fee_rate, sender=executor) + # coordinator.setFeeRatePerSecond(fee_rate, sender=executor) balance_before = token.balanceOf(beta_program_initiator.address) tx = beta_program_initiator.executeInitiationRequest(0, sender=executor) assert beta_program_initiator.requests(0)[RITUAL_ID_SLOT] == 1 assert beta_program_initiator.requests(1)[RITUAL_ID_SLOT] == 0 balance_after = token.balanceOf(beta_program_initiator.address) assert balance_before - balance_after == ritual_cost_1 - assert token.balanceOf(coordinator.address) == ritual_cost_2 + ritual_cost_1 + assert token.balanceOf(fee_model.address) == ritual_cost_2 + ritual_cost_1 assert coordinator.getRitualsLength() == 2 assert coordinator.getProviders(1) == nodes_1 @@ -323,30 +319,29 @@ def test_execute(accounts, beta_program_initiator, token, coordinator, executor) assert ritual[0] == beta_program_initiator.address assert ritual[1] == authority_1 assert ritual[2] == duration_1 - assert ritual[3] == access_controller_1 - assert ritual[4] == 1 - assert ritual[5] == ritual_cost_1 + assert ritual[3] == 1 + # assert ritual[4] == ritual_cost_1 + assert ritual[5] == fee_model.address events = beta_program_initiator.RequestExecuted.from_receipt(tx) assert events == [beta_program_initiator.RequestExecuted(0, 1)] -def test_refund(accounts, beta_program_initiator, token, coordinator, executor): +def test_refund(accounts, beta_program_initiator, token, coordinator, executor, fee_model): ( initiator_1, initiator_2, authority, node_1, node_2, - access_controller, *everyone_else, ) = accounts[2:] nodes = [node_1, node_2] duration_1 = DAY_IN_SECONDS - ritual_cost_1 = coordinator.getRitualInitiationCost(nodes, duration_1) + ritual_cost_1 = fee_model.getRitualInitiationCost(len(nodes), duration_1) duration_2 = 3 * duration_1 - ritual_cost_2 = coordinator.getRitualInitiationCost(nodes, duration_2) + ritual_cost_2 = fee_model.getRitualInitiationCost(len(nodes), duration_2) token.mint(initiator_1, 10 * ritual_cost_1, sender=initiator_1) token.approve(beta_program_initiator.address, 10 * ritual_cost_1, sender=initiator_1) @@ -359,13 +354,13 @@ def test_refund(accounts, beta_program_initiator, token, coordinator, executor): # Register three requests beta_program_initiator.registerInitiationRequest( - nodes, authority, duration_1, access_controller, sender=initiator_1 + nodes, authority, duration_1, sender=initiator_1 ) beta_program_initiator.registerInitiationRequest( - nodes, authority, duration_2, access_controller, sender=initiator_2 + nodes, authority, duration_2, sender=initiator_2 ) beta_program_initiator.registerInitiationRequest( - nodes, authority, duration_2, access_controller, sender=initiator_1 + nodes, authority, duration_2, sender=initiator_1 ) # Can't refund not executed request @@ -396,17 +391,17 @@ def test_refund(accounts, beta_program_initiator, token, coordinator, executor): assert token.balanceOf(beta_program_initiator.address) == 0 initiator_1_balance_before = token.balanceOf(initiator_1) - coordinator_balance_before = token.balanceOf(coordinator.address) - assert coordinator_balance_before == ritual_cost_1 + ritual_cost_2 - pending_fees_1_before = coordinator.pendingFees(request_0_ritual_id) + fee_model_balance_before = token.balanceOf(fee_model.address) + assert fee_model_balance_before == ritual_cost_1 + ritual_cost_2 + pending_fees_1_before = fee_model.pendingFees(request_0_ritual_id) assert pending_fees_1_before == ritual_cost_1 tx = beta_program_initiator.refundFailedRequest(0, sender=initiator_2) - coordinator_balance_after = token.balanceOf(coordinator.address) - fee_deduction_1 = coordinator.feeDeduction(pending_fees_1_before, duration_1) - pending_fees_1_after = coordinator.pendingFees(request_0_ritual_id) - assert coordinator_balance_after == ritual_cost_2 + fee_deduction_1 + fee_model_balance_after = token.balanceOf(fee_model.address) + fee_deduction_1 = fee_model.feeDeduction(pending_fees_1_before, duration_1) + pending_fees_1_after = fee_model.pendingFees(request_0_ritual_id) + assert fee_model_balance_after == ritual_cost_2 + fee_deduction_1 assert pending_fees_1_after == 0 assert token.balanceOf(beta_program_initiator.address) == 0 @@ -428,15 +423,15 @@ def test_refund(accounts, beta_program_initiator, token, coordinator, executor): assert token.balanceOf(beta_program_initiator.address) == 0 initiator_2_balance_before = token.balanceOf(initiator_2) - coordinator_balance_before = token.balanceOf(coordinator.address) - assert coordinator_balance_before == ritual_cost_2 + fee_deduction_1 - pending_fees_2_before = coordinator.pendingFees(request_1_ritual_id) + fee_model_balance_before = token.balanceOf(fee_model.address) + assert fee_model_balance_before == ritual_cost_2 + fee_deduction_1 + pending_fees_2_before = fee_model.pendingFees(request_1_ritual_id) assert pending_fees_2_before == ritual_cost_2 - coordinator.processPendingFee(request_1_ritual_id, sender=initiator_1) - assert coordinator.pendingFees(request_1_ritual_id) == 0 + fee_model.processPendingFee(request_1_ritual_id, sender=initiator_1) + assert fee_model.pendingFees(request_1_ritual_id) == 0 - fee_deduction_2 = coordinator.feeDeduction(pending_fees_2_before, duration_2) + fee_deduction_2 = fee_model.feeDeduction(pending_fees_2_before, duration_2) refund_2 = ritual_cost_2 - fee_deduction_2 assert token.balanceOf(beta_program_initiator.address) == refund_2 diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index 809eb0f55..4c919945f 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -4,7 +4,6 @@ import ape import pytest from eth_account import Account -from eth_account.messages import encode_defunct from hexbytes import HexBytes from web3 import Web3 @@ -87,12 +86,10 @@ def erc20(project, initiator): @pytest.fixture() -def coordinator(project, deployer, application, erc20, initiator, oz_dependency): +def coordinator(project, deployer, application, initiator, oz_dependency): admin = deployer contract = project.Coordinator.deploy( application.address, - erc20.address, - FEE_RATE, sender=deployer, ) @@ -110,8 +107,12 @@ def coordinator(project, deployer, application, erc20, initiator, oz_dependency) @pytest.fixture() -def global_allow_list(project, deployer, coordinator): - contract = project.GlobalAllowList.deploy(coordinator.address, sender=deployer) +def fee_model(project, deployer, coordinator, erc20, treasury): + contract = project.FlatRateFeeModel.deploy( + coordinator.address, erc20.address, FEE_RATE, sender=deployer + ) + coordinator.grantRole(coordinator.TREASURY_ROLE(), treasury, sender=deployer) + coordinator.approveFeeModel(contract.address, sender=treasury) return contract @@ -121,27 +122,21 @@ def test_initial_parameters(coordinator): assert coordinator.numberOfRituals() == 0 -def test_invalid_initiate_ritual( - project, coordinator, nodes, accounts, initiator, global_allow_list -): +def test_invalid_initiate_ritual(project, coordinator, nodes, accounts, initiator, fee_model): with ape.reverts("Sender can't initiate ritual"): sender = accounts[3] - coordinator.initiateRitual( - nodes, sender, DURATION, global_allow_list.address, sender=sender - ) + coordinator.initiateRitual(fee_model.address, nodes, sender, DURATION, sender=sender) with ape.reverts("Invalid number of nodes"): coordinator.initiateRitual( - nodes[:5] * 20, initiator, DURATION, global_allow_list.address, sender=initiator + fee_model.address, nodes[:5] * 20, initiator, DURATION, sender=initiator ) with ape.reverts("Invalid ritual duration"): - coordinator.initiateRitual(nodes, initiator, 0, global_allow_list.address, sender=initiator) + coordinator.initiateRitual(fee_model.address, nodes, initiator, 0, sender=initiator) with ape.reverts("Provider has not set their public key"): - coordinator.initiateRitual( - nodes, initiator, DURATION, global_allow_list.address, sender=initiator - ) + coordinator.initiateRitual(fee_model.address, nodes, initiator, DURATION, sender=initiator) for node in nodes: public_key = gen_public_key() @@ -149,40 +144,34 @@ def test_invalid_initiate_ritual( with ape.reverts("Providers must be sorted"): coordinator.initiateRitual( - nodes[1:] + [nodes[0]], initiator, DURATION, global_allow_list.address, sender=initiator + fee_model.address, nodes[1:] + [nodes[0]], initiator, DURATION, sender=initiator ) with ape.reverts(project.NuCypherToken.ERC20InsufficientAllowance): # Sender didn't approve enough tokens - coordinator.initiateRitual( - nodes, initiator, DURATION, global_allow_list.address, sender=initiator - ) + coordinator.initiateRitual(fee_model.address, nodes, initiator, DURATION, sender=initiator) -def initiate_ritual(coordinator, erc20, allow_logic, authority, nodes): +def initiate_ritual(coordinator, fee_model, erc20, authority, nodes): for node in nodes: public_key = gen_public_key() assert not coordinator.isProviderPublicKeySet(node) coordinator.setProviderPublicKey(public_key, sender=node) assert coordinator.isProviderPublicKeySet(node) - cost = coordinator.getRitualInitiationCost(nodes, DURATION) - erc20.approve(coordinator.address, cost, sender=authority) - tx = coordinator.initiateRitual( - nodes, authority, DURATION, allow_logic.address, sender=authority - ) + cost = fee_model.getRitualInitiationCost(len(nodes), DURATION) + erc20.approve(fee_model.address, cost, sender=authority) + tx = coordinator.initiateRitual(fee_model, nodes, authority, DURATION, sender=authority) return authority, tx -def test_initiate_ritual( - coordinator, nodes, initiator, erc20, global_allow_list, deployer, treasury -): +def test_initiate_ritual(coordinator, nodes, initiator, erc20, fee_model, deployer, treasury): authority, tx = initiate_ritual( coordinator=coordinator, + fee_model=fee_model, erc20=erc20, authority=initiator, nodes=nodes, - allow_logic=global_allow_list, ) ritualID = 0 @@ -205,21 +194,20 @@ def test_initiate_ritual( assert ritual_struct[6] == len(nodes) assert ritual_struct[7] == 1 + len(nodes) // 2 # threshold assert not ritual_struct[8] # aggregationMismatch - assert ritual_struct[9] == global_allow_list.address # accessController + # assert ritual_struct[9] == global_allow_list.address # accessController assert ritual_struct[10] == (b"\x00" * 32, b"\x00" * 16) # publicKey assert not ritual_struct[11] # aggregatedTranscript - fee = coordinator.getRitualInitiationCost(nodes, DURATION) - assert erc20.balanceOf(coordinator) == fee - assert coordinator.totalPendingFees() == fee - assert coordinator.pendingFees(ritualID) == fee + fee = fee_model.getRitualInitiationCost(len(nodes), DURATION) + assert erc20.balanceOf(fee_model) == fee + assert fee_model.totalPendingFees() == fee + assert fee_model.pendingFees(ritualID) == fee - with ape.reverts(access_control_error_message(treasury.address, coordinator.TREASURY_ROLE())): - coordinator.withdrawTokens(erc20.address, 1, sender=treasury) + with ape.reverts(): + fee_model.withdrawTokens(1, sender=treasury) - coordinator.grantRole(coordinator.TREASURY_ROLE(), treasury, sender=deployer) with ape.reverts("Can't withdraw pending fees"): - coordinator.withdrawTokens(erc20.address, 1, sender=treasury) + fee_model.withdrawTokens(1, sender=deployer) def test_provider_public_key(coordinator, nodes): @@ -241,13 +229,13 @@ def test_provider_public_key(coordinator, nodes): assert coordinator.getProviderPublicKey(selected_provider, ritual_id) == public_key -def test_post_transcript(coordinator, nodes, initiator, erc20, global_allow_list): +def test_post_transcript(coordinator, nodes, initiator, erc20, fee_model): initiate_ritual( coordinator=coordinator, + fee_model=fee_model, erc20=erc20, authority=initiator, nodes=nodes, - allow_logic=global_allow_list, ) transcript = os.urandom(transcript_size(len(nodes), len(nodes))) @@ -271,15 +259,13 @@ def test_post_transcript(coordinator, nodes, initiator, erc20, global_allow_list assert coordinator.getRitualState(0) == RitualState.DKG_AWAITING_AGGREGATIONS -def test_post_transcript_but_not_part_of_ritual( - coordinator, nodes, initiator, erc20, global_allow_list -): +def test_post_transcript_but_not_part_of_ritual(coordinator, nodes, initiator, erc20, fee_model): initiate_ritual( coordinator=coordinator, + fee_model=fee_model, erc20=erc20, authority=initiator, nodes=nodes, - allow_logic=global_allow_list, ) transcript = os.urandom(transcript_size(len(nodes), len(nodes))) @@ -288,14 +274,14 @@ def test_post_transcript_but_not_part_of_ritual( def test_post_transcript_but_already_posted_transcript( - coordinator, nodes, initiator, erc20, global_allow_list + coordinator, nodes, initiator, erc20, fee_model ): initiate_ritual( coordinator=coordinator, + fee_model=fee_model, erc20=erc20, authority=initiator, nodes=nodes, - allow_logic=global_allow_list, ) transcript = os.urandom(transcript_size(len(nodes), len(nodes))) coordinator.postTranscript(0, transcript, sender=nodes[0]) @@ -304,14 +290,14 @@ def test_post_transcript_but_already_posted_transcript( def test_post_transcript_but_not_waiting_for_transcripts( - coordinator, nodes, initiator, erc20, global_allow_list + coordinator, nodes, initiator, erc20, fee_model ): initiate_ritual( coordinator=coordinator, + fee_model=fee_model, erc20=erc20, authority=initiator, nodes=nodes, - allow_logic=global_allow_list, ) transcript = os.urandom(transcript_size(len(nodes), len(nodes))) for node in nodes: @@ -321,13 +307,13 @@ def test_post_transcript_but_not_waiting_for_transcripts( coordinator.postTranscript(0, transcript, sender=nodes[1]) -def test_get_participants(coordinator, nodes, initiator, erc20, global_allow_list): +def test_get_participants(coordinator, nodes, initiator, erc20, fee_model): initiate_ritual( coordinator=coordinator, + fee_model=fee_model, erc20=erc20, authority=initiator, nodes=nodes, - allow_logic=global_allow_list, ) transcript = os.urandom(transcript_size(len(nodes), len(nodes))) @@ -374,13 +360,13 @@ def test_get_participants(coordinator, nodes, initiator, erc20, global_allow_lis assert index == len(nodes) -def test_get_participant(nodes, coordinator, initiator, erc20, global_allow_list): +def test_get_participant(nodes, coordinator, initiator, erc20, fee_model): initiate_ritual( coordinator=coordinator, + fee_model=fee_model, erc20=erc20, authority=initiator, nodes=nodes, - allow_logic=global_allow_list, ) transcript = os.urandom(transcript_size(len(nodes), len(nodes))) @@ -420,15 +406,13 @@ def test_get_participant(nodes, coordinator, initiator, erc20, global_allow_list coordinator.getParticipantFromProvider(0, new_account.address) -def test_post_aggregation( - coordinator, nodes, initiator, erc20, global_allow_list, treasury, deployer -): +def test_post_aggregation(coordinator, nodes, initiator, erc20, fee_model, treasury, deployer): initiate_ritual( coordinator=coordinator, + fee_model=fee_model, erc20=erc20, authority=initiator, nodes=nodes, - allow_logic=global_allow_list, ) ritualID = 0 transcript = os.urandom(transcript_size(len(nodes), len(nodes))) @@ -464,29 +448,28 @@ def test_post_aggregation( assert retrieved_public_key == dkg_public_key assert coordinator.getRitualIdFromPublicKey(dkg_public_key) == ritualID - fee = coordinator.getRitualInitiationCost(nodes, DURATION) - assert erc20.balanceOf(coordinator) == fee - assert coordinator.totalPendingFees() == 0 - assert coordinator.pendingFees(ritualID) == 0 + fee_model.processPendingFee(ritualID, sender=treasury) + fee = fee_model.getRitualInitiationCost(len(nodes), DURATION) + assert erc20.balanceOf(fee_model) == fee + assert fee_model.totalPendingFees() == 0 + assert fee_model.pendingFees(ritualID) == 0 - coordinator.grantRole(coordinator.TREASURY_ROLE(), treasury, sender=deployer) with ape.reverts("Can't withdraw pending fees"): - coordinator.withdrawTokens(erc20.address, fee + 1, sender=treasury) - coordinator.withdrawTokens(erc20.address, fee, sender=treasury) + fee_model.withdrawTokens(fee + 1, sender=deployer) + fee_model.withdrawTokens(fee, sender=deployer) def test_post_aggregation_fails( - coordinator, nodes, initiator, erc20, global_allow_list, treasury, deployer + coordinator, nodes, initiator, erc20, fee_model, treasury, deployer ): - coordinator.grantRole(coordinator.TREASURY_ROLE(), treasury, sender=deployer) initiator_balance_before_payment = erc20.balanceOf(initiator) initiate_ritual( coordinator=coordinator, + fee_model=fee_model, erc20=erc20, authority=initiator, nodes=nodes, - allow_logic=global_allow_list, ) ritualID = 0 transcript = os.urandom(transcript_size(len(nodes), len(nodes))) @@ -513,119 +496,121 @@ def test_post_aggregation_fails( assert events == [coordinator.EndRitual(ritualId=ritualID, successful=False)] # Fees are still pending - fee = coordinator.getRitualInitiationCost(nodes, DURATION) - assert erc20.balanceOf(coordinator) == fee - assert coordinator.totalPendingFees() == fee - pending_fee = coordinator.pendingFees(ritualID) + fee = fee_model.getRitualInitiationCost(len(nodes), DURATION) + assert erc20.balanceOf(fee_model) == fee + assert fee_model.totalPendingFees() == fee + pending_fee = fee_model.pendingFees(ritualID) assert pending_fee == fee with ape.reverts("Can't withdraw pending fees"): - coordinator.withdrawTokens(erc20.address, 1, sender=treasury) + fee_model.withdrawTokens(1, sender=deployer) # Anyone can trigger processing of pending fees initiator_balance_before_refund = erc20.balanceOf(initiator) assert initiator_balance_before_refund == initiator_balance_before_payment - fee - coordinator.processPendingFee(ritualID, sender=treasury) + fee_model.processPendingFee(ritualID, sender=treasury) initiator_balance_after_refund = erc20.balanceOf(initiator) - coordinator_balance_after_refund = erc20.balanceOf(coordinator) + fee_model_balance_after_refund = erc20.balanceOf(fee_model) refund = initiator_balance_after_refund - initiator_balance_before_refund - assert refund == fee - coordinator.feeDeduction(pending_fee, DURATION) - assert coordinator_balance_after_refund + refund == fee - assert coordinator.totalPendingFees() == 0 - assert coordinator.pendingFees(ritualID) == 0 - coordinator.withdrawTokens(erc20.address, coordinator_balance_after_refund, sender=treasury) - - -def test_authorize_using_global_allow_list( - coordinator, nodes, deployer, initiator, erc20, global_allow_list -): - initiate_ritual( - coordinator=coordinator, - erc20=erc20, - authority=initiator, - nodes=nodes, - allow_logic=global_allow_list, - ) - - # This block mocks the signature of a threshold decryption request - w3 = Web3() - data = os.urandom(32) - digest = Web3.keccak(data) - signable_message = encode_defunct(digest) - signed_digest = w3.eth.account.sign_message(signable_message, private_key=deployer.private_key) - signature = signed_digest.signature - - # Not authorized - assert not global_allow_list.isAuthorized(0, bytes(signature), bytes(digest)) - - # Negative test cases for authorization - with ape.reverts("Only ritual authority is permitted"): - global_allow_list.authorize(0, [deployer.address], sender=deployer) - - with ape.reverts("Only active rituals can set authorizations"): - global_allow_list.authorize(0, [deployer.address], sender=initiator) - - with ape.reverts("Ritual not active"): - coordinator.isEncryptionAuthorized(0, bytes(signature), bytes(digest)) - - # Finalize ritual - transcript = os.urandom(transcript_size(len(nodes), len(nodes))) - for node in nodes: - coordinator.postTranscript(0, transcript, sender=node) - - aggregated = transcript - decryption_request_static_keys = [os.urandom(42) for _ in nodes] - dkg_public_key = (os.urandom(32), os.urandom(16)) - for i, node in enumerate(nodes): - coordinator.postAggregation( - 0, aggregated, dkg_public_key, decryption_request_static_keys[i], sender=node - ) - - # Actually authorize - tx = global_allow_list.authorize(0, [deployer.address], sender=initiator) - - # Authorized - assert global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) - assert coordinator.isEncryptionAuthorized(0, bytes(signature), bytes(data)) - events = global_allow_list.AddressAuthorizationSet.from_receipt(tx) - assert events == [ - global_allow_list.AddressAuthorizationSet( - ritualId=0, _address=deployer.address, isAuthorized=True - ) - ] - - # Deauthorize - tx = global_allow_list.deauthorize(0, [deployer.address], sender=initiator) - - assert not global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) - assert not coordinator.isEncryptionAuthorized(0, bytes(signature), bytes(data)) - events = global_allow_list.AddressAuthorizationSet.from_receipt(tx) - assert events == [ - global_allow_list.AddressAuthorizationSet( - ritualId=0, _address=deployer.address, isAuthorized=False - ) - ] - - # Reauthorize in batch - addresses_to_authorize = [deployer.address, initiator.address] - tx = global_allow_list.authorize(0, addresses_to_authorize, sender=initiator) - signed_digest = w3.eth.account.sign_message(signable_message, private_key=initiator.private_key) - initiator_signature = signed_digest.signature - assert global_allow_list.isAuthorized(0, bytes(initiator_signature), bytes(data)) - assert coordinator.isEncryptionAuthorized(0, bytes(initiator_signature), bytes(data)) - - assert global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) - assert coordinator.isEncryptionAuthorized(0, bytes(signature), bytes(data)) - - events = global_allow_list.AddressAuthorizationSet.from_receipt(tx) - assert events == [ - global_allow_list.AddressAuthorizationSet( - ritualId=0, _address=deployer.address, isAuthorized=True - ), - # TODO was this originally supposed to True (not sure why it passed before) - global_allow_list.AddressAuthorizationSet( - ritualId=0, _address=initiator.address, isAuthorized=True - ), - ] + assert refund == fee - fee_model.feeDeduction(pending_fee, DURATION) + assert fee_model_balance_after_refund + refund == fee + assert fee_model.totalPendingFees() == 0 + assert fee_model.pendingFees(ritualID) == 0 + fee_model.withdrawTokens(fee_model_balance_after_refund, sender=deployer) + + +# def test_authorize_using_global_allow_list( +# coordinator, nodes, deployer, initiator, erc20, fee_model +# ): +# initiate_ritual( +# coordinator=coordinator, +# fee_model=fee_model, +# erc20=erc20, +# authority=initiator, +# nodes=nodes, +# ) + +# # This block mocks the signature of a threshold decryption request +# w3 = Web3() +# data = os.urandom(32) +# digest = Web3.keccak(data) +# signable_message = encode_defunct(digest) +# signed_digest = +# w3.eth.account.sign_message(signable_message, private_key=deployer.private_key) +# signature = signed_digest.signature + +# # Not authorized +# assert not global_allow_list.isAuthorized(0, bytes(signature), bytes(digest)) + +# # Negative test cases for authorization +# with ape.reverts("Only ritual authority is permitted"): +# global_allow_list.authorize(0, [deployer.address], sender=deployer) + +# with ape.reverts("Only active rituals can set authorizations"): +# global_allow_list.authorize(0, [deployer.address], sender=initiator) + +# with ape.reverts("Ritual not active"): +# coordinator.isEncryptionAuthorized(0, bytes(signature), bytes(digest)) + +# # Finalize ritual +# transcript = os.urandom(transcript_size(len(nodes), len(nodes))) +# for node in nodes: +# coordinator.postTranscript(0, transcript, sender=node) + +# aggregated = transcript +# decryption_request_static_keys = [os.urandom(42) for _ in nodes] +# dkg_public_key = (os.urandom(32), os.urandom(16)) +# for i, node in enumerate(nodes): +# coordinator.postAggregation( +# 0, aggregated, dkg_public_key, decryption_request_static_keys[i], sender=node +# ) + +# # Actually authorize +# tx = global_allow_list.authorize(0, [deployer.address], sender=initiator) + +# # Authorized +# assert global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) +# assert coordinator.isEncryptionAuthorized(0, bytes(signature), bytes(data)) +# events = global_allow_list.AddressAuthorizationSet.from_receipt(tx) +# assert events == [ +# global_allow_list.AddressAuthorizationSet( +# ritualId=0, _address=deployer.address, isAuthorized=True +# ) +# ] + +# # Deauthorize +# tx = global_allow_list.deauthorize(0, [deployer.address], sender=initiator) + +# assert not global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) +# assert not coordinator.isEncryptionAuthorized(0, bytes(signature), bytes(data)) +# events = global_allow_list.AddressAuthorizationSet.from_receipt(tx) +# assert events == [ +# global_allow_list.AddressAuthorizationSet( +# ritualId=0, _address=deployer.address, isAuthorized=False +# ) +# ] + +# # Reauthorize in batch +# addresses_to_authorize = [deployer.address, initiator.address] +# tx = global_allow_list.authorize(0, addresses_to_authorize, sender=initiator) +# signed_digest = +# w3.eth.account.sign_message(signable_message, private_key=initiator.private_key) +# initiator_signature = signed_digest.signature +# assert global_allow_list.isAuthorized(0, bytes(initiator_signature), bytes(data)) +# assert coordinator.isEncryptionAuthorized(0, bytes(initiator_signature), bytes(data)) + +# assert global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) +# assert coordinator.isEncryptionAuthorized(0, bytes(signature), bytes(data)) + +# events = global_allow_list.AddressAuthorizationSet.from_receipt(tx) +# assert events == [ +# global_allow_list.AddressAuthorizationSet( +# ritualId=0, _address=deployer.address, isAuthorized=True +# ), +# # TODO was this originally supposed to True (not sure why it passed before) +# global_allow_list.AddressAuthorizationSet( +# ritualId=0, _address=initiator.address, isAuthorized=True +# ), +# ] From a277c330c11b86c5211badc39607a82878ec946d Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Wed, 15 May 2024 13:16:14 -0400 Subject: [PATCH 025/105] Moves global allow list test to the separate file --- tests/test_global_allow_list.py | 220 ++++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 tests/test_global_allow_list.py diff --git a/tests/test_global_allow_list.py b/tests/test_global_allow_list.py new file mode 100644 index 000000000..736d88608 --- /dev/null +++ b/tests/test_global_allow_list.py @@ -0,0 +1,220 @@ +import os +from enum import IntEnum + +import ape +import pytest +from eth_account.messages import encode_defunct +from web3 import Web3 + +TIMEOUT = 1000 +MAX_DKG_SIZE = 31 +FEE_RATE = 42 +ERC20_SUPPLY = 10**24 +DURATION = 48 * 60 * 60 +ONE_DAY = 24 * 60 * 60 + +RitualState = IntEnum( + "RitualState", + [ + "NON_INITIATED", + "DKG_AWAITING_TRANSCRIPTS", + "DKG_AWAITING_AGGREGATIONS", + "DKG_TIMEOUT", + "DKG_INVALID", + "ACTIVE", + "EXPIRED", + ], + start=0, +) + + +# This formula returns an approximated size +# To have a representative size, create transcripts with `nucypher-core` +def transcript_size(shares, threshold): + return int(424 + 240 * (shares / 2) + 50 * (threshold)) + + +def gen_public_key(): + return (os.urandom(32), os.urandom(32), os.urandom(32)) + + +def access_control_error_message(address, role=None): + role = role or b"\x00" * 32 + return f"account={address}, neededRole={role}" + + +@pytest.fixture(scope="module") +def nodes(accounts): + return sorted(accounts[:MAX_DKG_SIZE], key=lambda x: x.address.lower()) + + +@pytest.fixture(scope="module") +def initiator(accounts): + initiator_index = MAX_DKG_SIZE + 1 + assert len(accounts) >= initiator_index + return accounts[initiator_index] + + +@pytest.fixture(scope="module") +def deployer(accounts): + deployer_index = MAX_DKG_SIZE + 2 + assert len(accounts) >= deployer_index + return accounts[deployer_index] + + +@pytest.fixture(scope="module") +def treasury(accounts): + treasury_index = MAX_DKG_SIZE + 3 + assert len(accounts) >= treasury_index + return accounts[treasury_index] + + +@pytest.fixture() +def application(project, deployer, nodes): + contract = project.ChildApplicationForCoordinatorMock.deploy(sender=deployer) + for n in nodes: + contract.updateOperator(n, n, sender=deployer) + contract.updateAuthorization(n, 42, sender=deployer) + return contract + + +@pytest.fixture() +def erc20(project, initiator): + token = project.TestToken.deploy(ERC20_SUPPLY, sender=initiator) + return token + + +@pytest.fixture() +def coordinator(project, deployer, application, initiator, oz_dependency): + admin = deployer + contract = project.Coordinator.deploy( + application.address, + sender=deployer, + ) + + encoded_initializer_function = contract.initialize.encode_input(TIMEOUT, MAX_DKG_SIZE, admin) + proxy = oz_dependency.TransparentUpgradeableProxy.deploy( + contract.address, + deployer, + encoded_initializer_function, + sender=deployer, + ) + proxy_contract = project.Coordinator.at(proxy.address) + + proxy_contract.grantRole(contract.INITIATOR_ROLE(), initiator, sender=admin) + return proxy_contract + + +@pytest.fixture() +def fee_model(project, deployer, coordinator, erc20, treasury): + contract = project.FlatRateFeeModel.deploy( + coordinator.address, erc20.address, FEE_RATE, sender=deployer + ) + coordinator.grantRole(coordinator.TREASURY_ROLE(), treasury, sender=deployer) + coordinator.approveFeeModel(contract.address, sender=treasury) + return contract + + +@pytest.fixture() +def global_allow_list(project, deployer, coordinator): + contract = project.GlobalAllowList.deploy(coordinator.address, sender=deployer) + return contract + + +def initiate_ritual(coordinator, fee_model, erc20, authority, nodes): + for node in nodes: + public_key = gen_public_key() + assert not coordinator.isProviderPublicKeySet(node) + coordinator.setProviderPublicKey(public_key, sender=node) + assert coordinator.isProviderPublicKeySet(node) + + cost = fee_model.getRitualInitiationCost(len(nodes), DURATION) + erc20.approve(fee_model.address, cost, sender=authority) + tx = coordinator.initiateRitual(fee_model, nodes, authority, DURATION, sender=authority) + return authority, tx + + +def test_authorize_using_global_allow_list( + coordinator, nodes, deployer, initiator, erc20, fee_model, global_allow_list +): + initiate_ritual( + coordinator=coordinator, + fee_model=fee_model, + erc20=erc20, + authority=initiator, + nodes=nodes, + ) + + # This block mocks the signature of a threshold decryption request + w3 = Web3() + data = os.urandom(32) + digest = Web3.keccak(data) + signable_message = encode_defunct(digest) + signed_digest = w3.eth.account.sign_message(signable_message, private_key=deployer.private_key) + signature = signed_digest.signature + + # Not authorized + assert not global_allow_list.isAuthorized(0, bytes(signature), bytes(digest)) + + # Negative test cases for authorization + with ape.reverts("Only ritual authority is permitted"): + global_allow_list.authorize(0, [deployer.address], sender=deployer) + + with ape.reverts("Only active rituals can set authorizations"): + global_allow_list.authorize(0, [deployer.address], sender=initiator) + + # Finalize ritual + transcript = os.urandom(transcript_size(len(nodes), len(nodes))) + for node in nodes: + coordinator.postTranscript(0, transcript, sender=node) + + aggregated = transcript + decryption_request_static_keys = [os.urandom(42) for _ in nodes] + dkg_public_key = (os.urandom(32), os.urandom(16)) + for i, node in enumerate(nodes): + coordinator.postAggregation( + 0, aggregated, dkg_public_key, decryption_request_static_keys[i], sender=node + ) + + # Actually authorize + tx = global_allow_list.authorize(0, [deployer.address], sender=initiator) + + # Authorized + assert global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) + events = global_allow_list.AddressAuthorizationSet.from_receipt(tx) + assert events == [ + global_allow_list.AddressAuthorizationSet( + ritualId=0, _address=deployer.address, isAuthorized=True + ) + ] + + # Deauthorize + tx = global_allow_list.deauthorize(0, [deployer.address], sender=initiator) + + assert not global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) + events = global_allow_list.AddressAuthorizationSet.from_receipt(tx) + assert events == [ + global_allow_list.AddressAuthorizationSet( + ritualId=0, _address=deployer.address, isAuthorized=False + ) + ] + + # Reauthorize in batch + addresses_to_authorize = [deployer.address, initiator.address] + tx = global_allow_list.authorize(0, addresses_to_authorize, sender=initiator) + signed_digest = w3.eth.account.sign_message(signable_message, private_key=initiator.private_key) + initiator_signature = signed_digest.signature + assert global_allow_list.isAuthorized(0, bytes(initiator_signature), bytes(data)) + + assert global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) + + events = global_allow_list.AddressAuthorizationSet.from_receipt(tx) + assert events == [ + global_allow_list.AddressAuthorizationSet( + ritualId=0, _address=deployer.address, isAuthorized=True + ), + # TODO was this originally supposed to True (not sure why it passed before) + global_allow_list.AddressAuthorizationSet( + ritualId=0, _address=initiator.address, isAuthorized=True + ), + ] From 803963ccc9adffb0201b95c9c087c221b0abb8b8 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Wed, 15 May 2024 16:59:29 -0400 Subject: [PATCH 026/105] FlatRateFeeModel: restrict payment method --- contracts/contracts/coordination/FlatRateFeeModel.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/contracts/coordination/FlatRateFeeModel.sol b/contracts/contracts/coordination/FlatRateFeeModel.sol index 08ec2107f..b60b350d7 100644 --- a/contracts/contracts/coordination/FlatRateFeeModel.sol +++ b/contracts/contracts/coordination/FlatRateFeeModel.sol @@ -53,6 +53,7 @@ contract FlatRateFeeModel is IFeeModel, Ownable { uint256 numberOfProviders, uint32 duration ) external override { + require(msg.sender == address(coordinator), "Only coordinator can call process payment"); uint256 ritualCost = getRitualInitiationCost(numberOfProviders, duration); require(ritualCost > 0, "Invalid ritual cost"); totalPendingFees += ritualCost; From 1364cd1928d7ae8cdb0f03dd0585392b43413b94 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Fri, 17 May 2024 11:46:24 -0400 Subject: [PATCH 027/105] Minor renames --- contracts/contracts/coordination/Subscription.sol | 2 +- scripts/deploy_managed_allow_list.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/contracts/coordination/Subscription.sol b/contracts/contracts/coordination/Subscription.sol index e472702e9..b9e9fc825 100644 --- a/contracts/contracts/coordination/Subscription.sol +++ b/contracts/contracts/coordination/Subscription.sol @@ -175,7 +175,7 @@ abstract contract Subscription { } /** - * @notice Calculates the available amount of fees that can be withdrawn be the beneficiary + * @notice Calculates the available amount of fees that can by withdrawn be the beneficiary * @return The available amount of fees */ function calculateAvailableAmountForBeneficiary() public view returns (uint256) { diff --git a/scripts/deploy_managed_allow_list.py b/scripts/deploy_managed_allow_list.py index a0a65de56..0288ccc4d 100644 --- a/scripts/deploy_managed_allow_list.py +++ b/scripts/deploy_managed_allow_list.py @@ -14,8 +14,8 @@ def main(): deployer = Deployer.from_yaml(filepath=CONSTRUCTOR_PARAMS_FILEPATH, verify=VERIFY) - mnaged_allow_list = deployer.deploy(project.ManagedAllowList) - deployments = [mnaged_allow_list] + managed_allow_list = deployer.deploy(project.ManagedAllowList) + deployments = [managed_allow_list] deployer.finalize(deployments=deployments) for domain in (LYNX, TAPIR): From 0baee79ea819cfbf3780fdac53b57edb0617c3ba Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Wed, 22 May 2024 12:38:18 -0400 Subject: [PATCH 028/105] Apply suggestions from code review Co-authored-by: Manuel Montenegro --- .../contracts/coordination/Subscription.sol | 2 +- tests/test_coordinator.py | 95 ------------------- 2 files changed, 1 insertion(+), 96 deletions(-) diff --git a/contracts/contracts/coordination/Subscription.sol b/contracts/contracts/coordination/Subscription.sol index b9e9fc825..6b40f07f6 100644 --- a/contracts/contracts/coordination/Subscription.sol +++ b/contracts/contracts/coordination/Subscription.sol @@ -175,7 +175,7 @@ abstract contract Subscription { } /** - * @notice Calculates the available amount of fees that can by withdrawn be the beneficiary + * @notice Calculates the available amount of fees that can be withdrawn by the beneficiary * @return The available amount of fees */ function calculateAvailableAmountForBeneficiary() public view returns (uint256) { diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index 4c919945f..e6c5f31a1 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -519,98 +519,3 @@ def test_post_aggregation_fails( assert fee_model.totalPendingFees() == 0 assert fee_model.pendingFees(ritualID) == 0 fee_model.withdrawTokens(fee_model_balance_after_refund, sender=deployer) - - -# def test_authorize_using_global_allow_list( -# coordinator, nodes, deployer, initiator, erc20, fee_model -# ): -# initiate_ritual( -# coordinator=coordinator, -# fee_model=fee_model, -# erc20=erc20, -# authority=initiator, -# nodes=nodes, -# ) - -# # This block mocks the signature of a threshold decryption request -# w3 = Web3() -# data = os.urandom(32) -# digest = Web3.keccak(data) -# signable_message = encode_defunct(digest) -# signed_digest = -# w3.eth.account.sign_message(signable_message, private_key=deployer.private_key) -# signature = signed_digest.signature - -# # Not authorized -# assert not global_allow_list.isAuthorized(0, bytes(signature), bytes(digest)) - -# # Negative test cases for authorization -# with ape.reverts("Only ritual authority is permitted"): -# global_allow_list.authorize(0, [deployer.address], sender=deployer) - -# with ape.reverts("Only active rituals can set authorizations"): -# global_allow_list.authorize(0, [deployer.address], sender=initiator) - -# with ape.reverts("Ritual not active"): -# coordinator.isEncryptionAuthorized(0, bytes(signature), bytes(digest)) - -# # Finalize ritual -# transcript = os.urandom(transcript_size(len(nodes), len(nodes))) -# for node in nodes: -# coordinator.postTranscript(0, transcript, sender=node) - -# aggregated = transcript -# decryption_request_static_keys = [os.urandom(42) for _ in nodes] -# dkg_public_key = (os.urandom(32), os.urandom(16)) -# for i, node in enumerate(nodes): -# coordinator.postAggregation( -# 0, aggregated, dkg_public_key, decryption_request_static_keys[i], sender=node -# ) - -# # Actually authorize -# tx = global_allow_list.authorize(0, [deployer.address], sender=initiator) - -# # Authorized -# assert global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) -# assert coordinator.isEncryptionAuthorized(0, bytes(signature), bytes(data)) -# events = global_allow_list.AddressAuthorizationSet.from_receipt(tx) -# assert events == [ -# global_allow_list.AddressAuthorizationSet( -# ritualId=0, _address=deployer.address, isAuthorized=True -# ) -# ] - -# # Deauthorize -# tx = global_allow_list.deauthorize(0, [deployer.address], sender=initiator) - -# assert not global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) -# assert not coordinator.isEncryptionAuthorized(0, bytes(signature), bytes(data)) -# events = global_allow_list.AddressAuthorizationSet.from_receipt(tx) -# assert events == [ -# global_allow_list.AddressAuthorizationSet( -# ritualId=0, _address=deployer.address, isAuthorized=False -# ) -# ] - -# # Reauthorize in batch -# addresses_to_authorize = [deployer.address, initiator.address] -# tx = global_allow_list.authorize(0, addresses_to_authorize, sender=initiator) -# signed_digest = -# w3.eth.account.sign_message(signable_message, private_key=initiator.private_key) -# initiator_signature = signed_digest.signature -# assert global_allow_list.isAuthorized(0, bytes(initiator_signature), bytes(data)) -# assert coordinator.isEncryptionAuthorized(0, bytes(initiator_signature), bytes(data)) - -# assert global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) -# assert coordinator.isEncryptionAuthorized(0, bytes(signature), bytes(data)) - -# events = global_allow_list.AddressAuthorizationSet.from_receipt(tx) -# assert events == [ -# global_allow_list.AddressAuthorizationSet( -# ritualId=0, _address=deployer.address, isAuthorized=True -# ), -# # TODO was this originally supposed to True (not sure why it passed before) -# global_allow_list.AddressAuthorizationSet( -# ritualId=0, _address=initiator.address, isAuthorized=True -# ), -# ] From 925301ed840215826bb8764bfb43d5788c18c801 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Sat, 25 May 2024 11:41:22 -0400 Subject: [PATCH 029/105] Return back storing accessControl in Coordinator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Núñez --- .../coordination/BetaProgramInitiator.sol | 12 ++- .../contracts/coordination/Coordinator.sol | 13 ++- .../test/BetaProgramInitiatorTestSet.sol | 5 +- tests/test_beta_program_initiator.py | 60 +++++++----- tests/test_coordinator.py | 91 +++++++++++++++---- tests/test_global_allow_list.py | 7 +- 6 files changed, 137 insertions(+), 51 deletions(-) diff --git a/contracts/contracts/coordination/BetaProgramInitiator.sol b/contracts/contracts/coordination/BetaProgramInitiator.sol index ec7b72c1e..1486b2f7f 100644 --- a/contracts/contracts/coordination/BetaProgramInitiator.sol +++ b/contracts/contracts/coordination/BetaProgramInitiator.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "./FlatRateFeeModel.sol"; - +import "./IEncryptionAuthorizer.sol"; import "./Coordinator.sol"; contract BetaProgramInitiator { @@ -17,6 +17,7 @@ contract BetaProgramInitiator { address[] providers, address authority, uint32 duration, + IEncryptionAuthorizer accessController, uint256 payment ); @@ -30,6 +31,7 @@ contract BetaProgramInitiator { address[] providers; address authority; uint32 duration; + IEncryptionAuthorizer accessController; address sender; uint32 ritualId; uint256 payment; @@ -64,7 +66,8 @@ contract BetaProgramInitiator { function registerInitiationRequest( address[] calldata providers, address authority, - uint32 duration + uint32 duration, + IEncryptionAuthorizer accessController ) external returns (uint256 requestIndex) { uint256 ritualCost = feeModel.getRitualInitiationCost(providers.length, duration); @@ -73,6 +76,7 @@ contract BetaProgramInitiator { request.providers = providers; request.authority = authority; request.duration = duration; + request.accessController = accessController; request.sender = msg.sender; request.ritualId = NO_RITUAL; request.payment = ritualCost; @@ -83,6 +87,7 @@ contract BetaProgramInitiator { providers, authority, duration, + accessController, ritualCost ); currency.safeTransferFrom(msg.sender, address(this), ritualCost); @@ -125,7 +130,8 @@ contract BetaProgramInitiator { feeModel, providers, request.authority, - duration + duration, + request.accessController ); request.ritualId = ritualId; emit RequestExecuted(requestIndex, ritualId); diff --git a/contracts/contracts/coordination/Coordinator.sol b/contracts/contracts/coordination/Coordinator.sol index e728e1750..2326ae79d 100644 --- a/contracts/contracts/coordination/Coordinator.sol +++ b/contracts/contracts/coordination/Coordinator.sol @@ -8,6 +8,7 @@ import "./IFeeModel.sol"; import "./IReimbursementPool.sol"; import "../lib/BLS12381.sol"; import "../../threshold/ITACoChildApplication.sol"; +import "./IEncryptionAuthorizer.sol"; /** * @title Coordinator @@ -74,7 +75,7 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable uint16 threshold; bool aggregationMismatch; // - address stub1; // former accessController + IEncryptionAuthorizer accessController; BLS12381.G1Point publicKey; bytes aggregatedTranscript; Participant[] participant; @@ -89,14 +90,14 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable bytes32 public constant TREASURY_ROLE = keccak256("TREASURY_ROLE"); ITACoChildApplication public immutable application; - uint96 private immutable minAuthorization; // todo remove + uint96 private immutable minAuthorization; // TODO use child app for checking eligibility Ritual[] public rituals; uint32 public timeout; uint16 public maxDkgSize; bool public isInitiationPublic; - uint256 public stub1; // former totalPendingFees + uint256 private stub1; // former totalPendingFees mapping(uint256 => uint256) private stub2; // former pendingFees address private stub3; // former feeModel @@ -113,7 +114,7 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable constructor(ITACoChildApplication _application) { application = _application; - minAuthorization = _application.minimumAuthorization(); // TODO remove + minAuthorization = _application.minimumAuthorization(); // TODO use child app for checking eligibility } /** @@ -267,7 +268,8 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable IFeeModel feeModel, address[] calldata providers, address authority, - uint32 duration + uint32 duration, + IEncryptionAuthorizer accessController ) external returns (uint32) { require(authority != address(0), "Invalid authority"); @@ -288,6 +290,7 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable ritual.threshold = getThresholdForRitualSize(length); ritual.initTimestamp = uint32(block.timestamp); ritual.endTimestamp = ritual.initTimestamp + duration; + ritual.accessController = accessController; address previous = address(0); for (uint256 i = 0; i < length; i++) { diff --git a/contracts/test/BetaProgramInitiatorTestSet.sol b/contracts/test/BetaProgramInitiatorTestSet.sol index 6b38c213b..838f08e30 100644 --- a/contracts/test/BetaProgramInitiatorTestSet.sol +++ b/contracts/test/BetaProgramInitiatorTestSet.sol @@ -16,6 +16,7 @@ contract CoordinatorForBetaProgramInitiatorMock { address[] providers; address authority; uint32 duration; + IEncryptionAuthorizer accessController; Coordinator.RitualState state; uint256 ritualCost; IFeeModel feeModel; @@ -47,13 +48,15 @@ contract CoordinatorForBetaProgramInitiatorMock { IFeeModel _feeModel, address[] calldata _providers, address _authority, - uint32 _duration + uint32 _duration, + IEncryptionAuthorizer _accessController ) external returns (uint32 ritualId) { Ritual storage ritual = rituals.push(); ritual.initiator = msg.sender; ritual.providers = _providers; ritual.authority = _authority; ritual.duration = _duration; + ritual.accessController = _accessController; ritual.feeModel = _feeModel; ritual.state = Coordinator.RitualState.DKG_AWAITING_TRANSCRIPTS; diff --git a/tests/test_beta_program_initiator.py b/tests/test_beta_program_initiator.py index e1b28d80c..c59c02a33 100644 --- a/tests/test_beta_program_initiator.py +++ b/tests/test_beta_program_initiator.py @@ -23,9 +23,10 @@ AUTHORITY_SLOT = 0 DURATION_SLOT = 1 -SENDER_SLOT = 2 -RITUAL_ID_SLOT = 3 -PAYMENT_SLOT = 4 +ACCESS_CONTROLLER_SLOT = 2 +SENDER_SLOT = 3 +RITUAL_ID_SLOT = 4 +PAYMENT_SLOT = 5 FEE_RATE = 42 RitualState = IntEnum( @@ -83,6 +84,7 @@ def test_register(accounts, beta_program_initiator, token, fee_model): authority, node_1, node_2, + access_controller, *everyone_else, ) = accounts[2:] no_ritual = beta_program_initiator.NO_RITUAL() @@ -94,20 +96,21 @@ def test_register(accounts, beta_program_initiator, token, fee_model): # Can't register request without token transfer approval with ape.reverts(): beta_program_initiator.registerInitiationRequest( - nodes, authority, duration, sender=initiator_1 + nodes, authority, duration, access_controller, sender=initiator_1 ) # Register request token.mint(initiator_1, 10 * ritual_cost, sender=initiator_1) token.approve(beta_program_initiator.address, 10 * ritual_cost, sender=initiator_1) tx = beta_program_initiator.registerInitiationRequest( - nodes, authority, duration, sender=initiator_1 + nodes, authority, duration, access_controller, sender=initiator_1 ) assert beta_program_initiator.getRequestsLength() == 1 assert beta_program_initiator.getProviders(0) == nodes request = beta_program_initiator.requests(0) assert request[AUTHORITY_SLOT] == authority assert request[DURATION_SLOT] == duration + assert request[ACCESS_CONTROLLER_SLOT] == access_controller assert request[SENDER_SLOT] == initiator_1 assert request[RITUAL_ID_SLOT] == no_ritual assert request[PAYMENT_SLOT] == ritual_cost @@ -121,6 +124,7 @@ def test_register(accounts, beta_program_initiator, token, fee_model): assert event.providers == [n.address for n in nodes] assert event.authority == authority assert event.duration == duration + assert event.accessController == access_controller assert event.payment == ritual_cost # Register another request @@ -131,13 +135,14 @@ def test_register(accounts, beta_program_initiator, token, fee_model): token.mint(initiator_2, ritual_cost_2, sender=initiator_2) token.approve(beta_program_initiator.address, ritual_cost_2, sender=initiator_2) tx = beta_program_initiator.registerInitiationRequest( - nodes, authority, duration, sender=initiator_2 + nodes, authority, duration, access_controller, sender=initiator_2 ) assert beta_program_initiator.getRequestsLength() == 2 assert beta_program_initiator.getProviders(1) == nodes request = beta_program_initiator.requests(1) assert request[AUTHORITY_SLOT] == authority assert request[DURATION_SLOT] == duration + assert request[ACCESS_CONTROLLER_SLOT] == access_controller assert request[SENDER_SLOT] == initiator_2 assert request[RITUAL_ID_SLOT] == no_ritual assert request[PAYMENT_SLOT] == ritual_cost_2 @@ -151,6 +156,7 @@ def test_register(accounts, beta_program_initiator, token, fee_model): assert event.providers == [n.address for n in nodes] assert event.authority == authority assert event.duration == duration + assert event.accessController == access_controller assert event.payment == ritual_cost_2 @@ -161,6 +167,7 @@ def test_cancel(accounts, beta_program_initiator, token, executor, fee_model): authority, node_1, node_2, + access_controller, *everyone_else, ) = accounts[2:] @@ -178,9 +185,15 @@ def test_cancel(accounts, beta_program_initiator, token, executor, fee_model): beta_program_initiator.cancelInitiationRequest(0, sender=executor) # Register three requests - beta_program_initiator.registerInitiationRequest(nodes, authority, duration, sender=initiator_1) - beta_program_initiator.registerInitiationRequest(nodes, authority, duration, sender=initiator_1) - beta_program_initiator.registerInitiationRequest(nodes, authority, duration, sender=initiator_2) + beta_program_initiator.registerInitiationRequest( + nodes, authority, duration, access_controller, sender=initiator_1 + ) + beta_program_initiator.registerInitiationRequest( + nodes, authority, duration, access_controller, sender=initiator_1 + ) + beta_program_initiator.registerInitiationRequest( + nodes, authority, duration, access_controller, sender=initiator_2 + ) # Only initiator or executor can cancel request with ape.reverts("Not allowed to cancel"): @@ -228,6 +241,8 @@ def test_execute(accounts, beta_program_initiator, token, coordinator, executor, authority_2, node_1, node_2, + access_controller_1, + access_controller_2, *everyone_else, ) = accounts[2:] no_ritual = beta_program_initiator.NO_RITUAL() @@ -250,13 +265,13 @@ def test_execute(accounts, beta_program_initiator, token, coordinator, executor, # Register three requests beta_program_initiator.registerInitiationRequest( - nodes_1, authority_1, duration_1, sender=initiator_1 + nodes_1, authority_1, duration_1, access_controller_1, sender=initiator_1 ) beta_program_initiator.registerInitiationRequest( - nodes_2, authority_2, duration_2, sender=initiator_2 + nodes_2, authority_2, duration_2, access_controller_2, sender=initiator_2 ) beta_program_initiator.registerInitiationRequest( - nodes_2, authority_1, duration_1, sender=initiator_1 + nodes_2, authority_1, duration_1, access_controller_2, sender=initiator_1 ) # Only executor can execute request @@ -283,9 +298,10 @@ def test_execute(accounts, beta_program_initiator, token, coordinator, executor, assert ritual[0] == beta_program_initiator.address assert ritual[1] == authority_2 assert ritual[2] == duration_2 - assert ritual[3] == 1 - # assert ritual[4] == ritual_cost_2 - assert ritual[5] == fee_model.address + assert ritual[3] == access_controller_2 + assert ritual[4] == 1 + # assert ritual[5] == ritual_cost_2 + assert ritual[6] == fee_model.address events = beta_program_initiator.RequestExecuted.from_receipt(tx) assert events == [beta_program_initiator.RequestExecuted(1, 0)] @@ -319,9 +335,10 @@ def test_execute(accounts, beta_program_initiator, token, coordinator, executor, assert ritual[0] == beta_program_initiator.address assert ritual[1] == authority_1 assert ritual[2] == duration_1 - assert ritual[3] == 1 - # assert ritual[4] == ritual_cost_1 - assert ritual[5] == fee_model.address + assert ritual[3] == access_controller_1 + assert ritual[4] == 1 + # assert ritual[5] == ritual_cost_1 + assert ritual[6] == fee_model.address events = beta_program_initiator.RequestExecuted.from_receipt(tx) assert events == [beta_program_initiator.RequestExecuted(0, 1)] @@ -334,6 +351,7 @@ def test_refund(accounts, beta_program_initiator, token, coordinator, executor, authority, node_1, node_2, + access_controller, *everyone_else, ) = accounts[2:] @@ -354,13 +372,13 @@ def test_refund(accounts, beta_program_initiator, token, coordinator, executor, # Register three requests beta_program_initiator.registerInitiationRequest( - nodes, authority, duration_1, sender=initiator_1 + nodes, authority, duration_1, access_controller, sender=initiator_1 ) beta_program_initiator.registerInitiationRequest( - nodes, authority, duration_2, sender=initiator_2 + nodes, authority, duration_2, access_controller, sender=initiator_2 ) beta_program_initiator.registerInitiationRequest( - nodes, authority, duration_2, sender=initiator_1 + nodes, authority, duration_2, access_controller, sender=initiator_1 ) # Can't refund not executed request diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index e6c5f31a1..3facc7d53 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -116,27 +116,51 @@ def fee_model(project, deployer, coordinator, erc20, treasury): return contract +@pytest.fixture() +def global_allow_list(project, deployer, coordinator): + contract = project.GlobalAllowList.deploy(coordinator.address, sender=deployer) + return contract + + def test_initial_parameters(coordinator): assert coordinator.maxDkgSize() == MAX_DKG_SIZE assert coordinator.timeout() == TIMEOUT assert coordinator.numberOfRituals() == 0 -def test_invalid_initiate_ritual(project, coordinator, nodes, accounts, initiator, fee_model): +def test_invalid_initiate_ritual( + project, coordinator, nodes, accounts, initiator, fee_model, global_allow_list +): with ape.reverts("Sender can't initiate ritual"): sender = accounts[3] - coordinator.initiateRitual(fee_model.address, nodes, sender, DURATION, sender=sender) + coordinator.initiateRitual( + fee_model.address, nodes, sender, DURATION, global_allow_list.address, sender=sender + ) with ape.reverts("Invalid number of nodes"): coordinator.initiateRitual( - fee_model.address, nodes[:5] * 20, initiator, DURATION, sender=initiator + fee_model.address, + nodes[:5] * 20, + initiator, + DURATION, + global_allow_list.address, + sender=initiator, ) with ape.reverts("Invalid ritual duration"): - coordinator.initiateRitual(fee_model.address, nodes, initiator, 0, sender=initiator) + coordinator.initiateRitual( + fee_model.address, nodes, initiator, 0, global_allow_list.address, sender=initiator + ) with ape.reverts("Provider has not set their public key"): - coordinator.initiateRitual(fee_model.address, nodes, initiator, DURATION, sender=initiator) + coordinator.initiateRitual( + fee_model.address, + nodes, + initiator, + DURATION, + global_allow_list.address, + sender=initiator, + ) for node in nodes: public_key = gen_public_key() @@ -144,15 +168,27 @@ def test_invalid_initiate_ritual(project, coordinator, nodes, accounts, initiato with ape.reverts("Providers must be sorted"): coordinator.initiateRitual( - fee_model.address, nodes[1:] + [nodes[0]], initiator, DURATION, sender=initiator + fee_model.address, + nodes[1:] + [nodes[0]], + initiator, + DURATION, + global_allow_list.address, + sender=initiator, ) with ape.reverts(project.NuCypherToken.ERC20InsufficientAllowance): # Sender didn't approve enough tokens - coordinator.initiateRitual(fee_model.address, nodes, initiator, DURATION, sender=initiator) + coordinator.initiateRitual( + fee_model.address, + nodes, + initiator, + DURATION, + global_allow_list.address, + sender=initiator, + ) -def initiate_ritual(coordinator, fee_model, erc20, authority, nodes): +def initiate_ritual(coordinator, fee_model, erc20, authority, nodes, allow_logic): for node in nodes: public_key = gen_public_key() assert not coordinator.isProviderPublicKeySet(node) @@ -161,17 +197,22 @@ def initiate_ritual(coordinator, fee_model, erc20, authority, nodes): cost = fee_model.getRitualInitiationCost(len(nodes), DURATION) erc20.approve(fee_model.address, cost, sender=authority) - tx = coordinator.initiateRitual(fee_model, nodes, authority, DURATION, sender=authority) + tx = coordinator.initiateRitual( + fee_model, nodes, authority, DURATION, allow_logic.address, sender=authority + ) return authority, tx -def test_initiate_ritual(coordinator, nodes, initiator, erc20, fee_model, deployer, treasury): +def test_initiate_ritual( + coordinator, nodes, initiator, erc20, fee_model, deployer, treasury, global_allow_list +): authority, tx = initiate_ritual( coordinator=coordinator, fee_model=fee_model, erc20=erc20, authority=initiator, nodes=nodes, + allow_logic=global_allow_list, ) ritualID = 0 @@ -194,7 +235,7 @@ def test_initiate_ritual(coordinator, nodes, initiator, erc20, fee_model, deploy assert ritual_struct[6] == len(nodes) assert ritual_struct[7] == 1 + len(nodes) // 2 # threshold assert not ritual_struct[8] # aggregationMismatch - # assert ritual_struct[9] == global_allow_list.address # accessController + assert ritual_struct[9] == global_allow_list.address # accessController assert ritual_struct[10] == (b"\x00" * 32, b"\x00" * 16) # publicKey assert not ritual_struct[11] # aggregatedTranscript @@ -229,13 +270,14 @@ def test_provider_public_key(coordinator, nodes): assert coordinator.getProviderPublicKey(selected_provider, ritual_id) == public_key -def test_post_transcript(coordinator, nodes, initiator, erc20, fee_model): +def test_post_transcript(coordinator, nodes, initiator, erc20, fee_model, global_allow_list): initiate_ritual( coordinator=coordinator, fee_model=fee_model, erc20=erc20, authority=initiator, nodes=nodes, + allow_logic=global_allow_list, ) transcript = os.urandom(transcript_size(len(nodes), len(nodes))) @@ -259,13 +301,16 @@ def test_post_transcript(coordinator, nodes, initiator, erc20, fee_model): assert coordinator.getRitualState(0) == RitualState.DKG_AWAITING_AGGREGATIONS -def test_post_transcript_but_not_part_of_ritual(coordinator, nodes, initiator, erc20, fee_model): +def test_post_transcript_but_not_part_of_ritual( + coordinator, nodes, initiator, erc20, fee_model, global_allow_list +): initiate_ritual( coordinator=coordinator, fee_model=fee_model, erc20=erc20, authority=initiator, nodes=nodes, + allow_logic=global_allow_list, ) transcript = os.urandom(transcript_size(len(nodes), len(nodes))) @@ -274,7 +319,7 @@ def test_post_transcript_but_not_part_of_ritual(coordinator, nodes, initiator, e def test_post_transcript_but_already_posted_transcript( - coordinator, nodes, initiator, erc20, fee_model + coordinator, nodes, initiator, erc20, fee_model, global_allow_list ): initiate_ritual( coordinator=coordinator, @@ -282,6 +327,7 @@ def test_post_transcript_but_already_posted_transcript( erc20=erc20, authority=initiator, nodes=nodes, + allow_logic=global_allow_list, ) transcript = os.urandom(transcript_size(len(nodes), len(nodes))) coordinator.postTranscript(0, transcript, sender=nodes[0]) @@ -290,7 +336,7 @@ def test_post_transcript_but_already_posted_transcript( def test_post_transcript_but_not_waiting_for_transcripts( - coordinator, nodes, initiator, erc20, fee_model + coordinator, nodes, initiator, erc20, fee_model, global_allow_list ): initiate_ritual( coordinator=coordinator, @@ -298,6 +344,7 @@ def test_post_transcript_but_not_waiting_for_transcripts( erc20=erc20, authority=initiator, nodes=nodes, + allow_logic=global_allow_list, ) transcript = os.urandom(transcript_size(len(nodes), len(nodes))) for node in nodes: @@ -307,13 +354,14 @@ def test_post_transcript_but_not_waiting_for_transcripts( coordinator.postTranscript(0, transcript, sender=nodes[1]) -def test_get_participants(coordinator, nodes, initiator, erc20, fee_model): +def test_get_participants(coordinator, nodes, initiator, erc20, fee_model, global_allow_list): initiate_ritual( coordinator=coordinator, fee_model=fee_model, erc20=erc20, authority=initiator, nodes=nodes, + allow_logic=global_allow_list, ) transcript = os.urandom(transcript_size(len(nodes), len(nodes))) @@ -360,13 +408,14 @@ def test_get_participants(coordinator, nodes, initiator, erc20, fee_model): assert index == len(nodes) -def test_get_participant(nodes, coordinator, initiator, erc20, fee_model): +def test_get_participant(nodes, coordinator, initiator, erc20, fee_model, global_allow_list): initiate_ritual( coordinator=coordinator, fee_model=fee_model, erc20=erc20, authority=initiator, nodes=nodes, + allow_logic=global_allow_list, ) transcript = os.urandom(transcript_size(len(nodes), len(nodes))) @@ -406,13 +455,16 @@ def test_get_participant(nodes, coordinator, initiator, erc20, fee_model): coordinator.getParticipantFromProvider(0, new_account.address) -def test_post_aggregation(coordinator, nodes, initiator, erc20, fee_model, treasury, deployer): +def test_post_aggregation( + coordinator, nodes, initiator, erc20, fee_model, treasury, deployer, global_allow_list +): initiate_ritual( coordinator=coordinator, fee_model=fee_model, erc20=erc20, authority=initiator, nodes=nodes, + allow_logic=global_allow_list, ) ritualID = 0 transcript = os.urandom(transcript_size(len(nodes), len(nodes))) @@ -460,7 +512,7 @@ def test_post_aggregation(coordinator, nodes, initiator, erc20, fee_model, treas def test_post_aggregation_fails( - coordinator, nodes, initiator, erc20, fee_model, treasury, deployer + coordinator, nodes, initiator, erc20, fee_model, treasury, deployer, global_allow_list ): initiator_balance_before_payment = erc20.balanceOf(initiator) @@ -470,6 +522,7 @@ def test_post_aggregation_fails( erc20=erc20, authority=initiator, nodes=nodes, + allow_logic=global_allow_list, ) ritualID = 0 transcript = os.urandom(transcript_size(len(nodes), len(nodes))) diff --git a/tests/test_global_allow_list.py b/tests/test_global_allow_list.py index 736d88608..1728e28e4 100644 --- a/tests/test_global_allow_list.py +++ b/tests/test_global_allow_list.py @@ -121,7 +121,7 @@ def global_allow_list(project, deployer, coordinator): return contract -def initiate_ritual(coordinator, fee_model, erc20, authority, nodes): +def initiate_ritual(coordinator, fee_model, erc20, authority, nodes, allow_logic): for node in nodes: public_key = gen_public_key() assert not coordinator.isProviderPublicKeySet(node) @@ -130,7 +130,9 @@ def initiate_ritual(coordinator, fee_model, erc20, authority, nodes): cost = fee_model.getRitualInitiationCost(len(nodes), DURATION) erc20.approve(fee_model.address, cost, sender=authority) - tx = coordinator.initiateRitual(fee_model, nodes, authority, DURATION, sender=authority) + tx = coordinator.initiateRitual( + fee_model, nodes, authority, DURATION, allow_logic.address, sender=authority + ) return authority, tx @@ -143,6 +145,7 @@ def test_authorize_using_global_allow_list( erc20=erc20, authority=initiator, nodes=nodes, + allow_logic=global_allow_list, ) # This block mocks the signature of a threshold decryption request From 378c818471046cc337f001bdb8434dd87b1fdb2f Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Wed, 22 May 2024 19:48:03 -0400 Subject: [PATCH 030/105] Adds fee model <-> allow list communication --- .../contracts/coordination/FlatRateFeeModel.sol | 14 ++++++++++++++ .../contracts/coordination/GlobalAllowList.sol | 14 ++++++++++---- contracts/contracts/coordination/IFeeModel.sol | 12 ++++++++++++ .../contracts/coordination/ManagedAllowList.sol | 8 ++++---- tests/test_global_allow_list.py | 6 ++++-- tests/test_managed_allow_list.py | 13 +++++++++++-- 6 files changed, 55 insertions(+), 12 deletions(-) diff --git a/contracts/contracts/coordination/FlatRateFeeModel.sol b/contracts/contracts/coordination/FlatRateFeeModel.sol index b60b350d7..a00324af0 100644 --- a/contracts/contracts/coordination/FlatRateFeeModel.sol +++ b/contracts/contracts/coordination/FlatRateFeeModel.sol @@ -98,4 +98,18 @@ contract FlatRateFeeModel is IFeeModel, Ownable { ); currency.safeTransfer(msg.sender, amount); } + + /** + * @dev This function is called before the setAuthorizations function + * @param ritualId The ID of the ritual + * @param addresses The addresses to be authorized + * @param value The authorization status + */ + function beforeSetAuthorization( + uint32 ritualId, + address[] calldata addresses, + bool value + ) external view { + // solhint-disable-previous-line no-empty-blocks + } } diff --git a/contracts/contracts/coordination/GlobalAllowList.sol b/contracts/contracts/coordination/GlobalAllowList.sol index f3bb52fa2..afbf19497 100644 --- a/contracts/contracts/coordination/GlobalAllowList.sol +++ b/contracts/contracts/coordination/GlobalAllowList.sol @@ -17,6 +17,7 @@ contract GlobalAllowList is IEncryptionAuthorizer { using ECDSA for bytes32; Coordinator public immutable coordinator; + IFeeModel public immutable feeModel; mapping(bytes32 => bool) internal authorizations; @@ -40,11 +41,16 @@ contract GlobalAllowList is IEncryptionAuthorizer { * @notice Sets the coordinator contract * @dev The coordinator contract cannot be a zero address and must have a valid number of rituals * @param _coordinator The address of the coordinator contract + * @param _feeModel The address of the fee model contract */ - constructor(Coordinator _coordinator) { - require(address(_coordinator) != address(0), "Coordinator cannot be zero address"); + constructor(Coordinator _coordinator, IFeeModel _feeModel) { + require( + address(_coordinator) != address(0) && address(_feeModel) != address(0), + "Coordinator cannot be zero address" + ); require(_coordinator.numberOfRituals() >= 0, "Invalid coordinator"); coordinator = _coordinator; + feeModel = _feeModel; } /** @@ -111,8 +117,8 @@ contract GlobalAllowList is IEncryptionAuthorizer { uint32 ritualId, address[] calldata addresses, bool value - ) internal view virtual { - // solhint-disable-previous-line no-empty-blocks + ) internal virtual { + feeModel.beforeSetAuthorization(ritualId, addresses, value); } /** diff --git a/contracts/contracts/coordination/IFeeModel.sol b/contracts/contracts/coordination/IFeeModel.sol index 4e9fe5ade..5275c7f69 100644 --- a/contracts/contracts/coordination/IFeeModel.sol +++ b/contracts/contracts/coordination/IFeeModel.sol @@ -22,4 +22,16 @@ interface IFeeModel { uint256 numberOfProviders, uint32 duration ) external; + + /** + * @dev This function is called before the setAuthorizations function + * @param ritualId The ID of the ritual + * @param addresses The addresses to be authorized + * @param value The authorization status + */ + function beforeSetAuthorization( + uint32 ritualId, + address[] calldata addresses, + bool value + ) external; } diff --git a/contracts/contracts/coordination/ManagedAllowList.sol b/contracts/contracts/coordination/ManagedAllowList.sol index f548a7105..67cea1621 100644 --- a/contracts/contracts/coordination/ManagedAllowList.sol +++ b/contracts/contracts/coordination/ManagedAllowList.sol @@ -39,9 +39,9 @@ contract ManagedAllowList is GlobalAllowList { */ constructor( Coordinator _coordinator, + IFeeModel _feeModel, UpfrontSubscriptionWithEncryptorsCap _subscription - ) GlobalAllowList(_coordinator) { - require(address(_coordinator) != address(0), "Coordinator cannot be the zero address"); + ) GlobalAllowList(_coordinator, _feeModel) { require(address(_subscription) != address(0), "Subscription cannot be the zero address"); subscription = _subscription; } @@ -88,9 +88,9 @@ contract ManagedAllowList is GlobalAllowList { uint32 ritualId, address[] calldata addresses, // TODO: Currently unused, remove? - // solhint-disable-next-line no-unused-vars bool value - ) internal view override { + ) internal override { + super._beforeSetAuthorization(ritualId, addresses, value); for (uint256 i = 0; i < addresses.length; i++) { require( authActions[ritualId] < diff --git a/tests/test_global_allow_list.py b/tests/test_global_allow_list.py index 1728e28e4..4d374c169 100644 --- a/tests/test_global_allow_list.py +++ b/tests/test_global_allow_list.py @@ -116,8 +116,10 @@ def fee_model(project, deployer, coordinator, erc20, treasury): @pytest.fixture() -def global_allow_list(project, deployer, coordinator): - contract = project.GlobalAllowList.deploy(coordinator.address, sender=deployer) +def global_allow_list(project, deployer, coordinator, fee_model): + contract = project.GlobalAllowList.deploy( + coordinator.address, fee_model.address, sender=deployer + ) return contract diff --git a/tests/test_managed_allow_list.py b/tests/test_managed_allow_list.py index 3d953c426..4e37c298d 100644 --- a/tests/test_managed_allow_list.py +++ b/tests/test_managed_allow_list.py @@ -4,6 +4,7 @@ RITUAL_ID = 0 ADMIN_CAP = 5 ERC20_SUPPLY = 10**24 +FEE_RATE = 42 @pytest.fixture(scope="module") @@ -52,9 +53,17 @@ def subscription(project, coordinator, fee_token, beneficiary, authority): @pytest.fixture() -def brand_new_managed_allow_list(project, coordinator, subscription, deployer, authority): +def fee_model(project, deployer, coordinator, fee_token): + contract = project.FlatRateFeeModel.deploy( + coordinator.address, fee_token.address, FEE_RATE, sender=deployer + ) + return contract + + +@pytest.fixture() +def brand_new_managed_allow_list(project, coordinator, subscription, fee_model, authority): return project.ManagedAllowList.deploy( - coordinator.address, subscription.address, sender=authority + coordinator.address, fee_model.address, subscription.address, sender=authority ) From c3d695b4119249ee1d88be5453bc44745960a3ff Mon Sep 17 00:00:00 2001 From: Victoria Date: Fri, 7 Jun 2024 20:43:31 +0200 Subject: [PATCH 031/105] Apply suggestions from code review Co-authored-by: Derek Pierre --- contracts/contracts/coordination/GlobalAllowList.sol | 2 +- contracts/contracts/coordination/Subscription.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/contracts/coordination/GlobalAllowList.sol b/contracts/contracts/coordination/GlobalAllowList.sol index afbf19497..d3be4198e 100644 --- a/contracts/contracts/coordination/GlobalAllowList.sol +++ b/contracts/contracts/coordination/GlobalAllowList.sol @@ -46,7 +46,7 @@ contract GlobalAllowList is IEncryptionAuthorizer { constructor(Coordinator _coordinator, IFeeModel _feeModel) { require( address(_coordinator) != address(0) && address(_feeModel) != address(0), - "Coordinator cannot be zero address" + "Contracts cannot be zero addresses" ); require(_coordinator.numberOfRituals() >= 0, "Invalid coordinator"); coordinator = _coordinator; diff --git a/contracts/contracts/coordination/Subscription.sol b/contracts/contracts/coordination/Subscription.sol index 6b40f07f6..65252762c 100644 --- a/contracts/contracts/coordination/Subscription.sol +++ b/contracts/contracts/coordination/Subscription.sol @@ -175,7 +175,7 @@ abstract contract Subscription { } /** - * @notice Calculates the available amount of fees that can be withdrawn by the beneficiary + * @notice Calculates the available fees that can be withdrawn by the beneficiary * @return The available amount of fees */ function calculateAvailableAmountForBeneficiary() public view returns (uint256) { From 4c42a9578ab29492d9fa861b44b7cfac3f646a85 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Tue, 28 May 2024 13:24:48 -0400 Subject: [PATCH 032/105] Initial commit with BqETHSubscription contract --- .../coordination/BqETHSubscription.sol | 151 ++++++++++++++++++ .../contracts/coordination/IFeeModel.sol | 9 +- tests/test_coordinator.py | 6 +- 3 files changed, 156 insertions(+), 10 deletions(-) create mode 100644 contracts/contracts/coordination/BqETHSubscription.sol diff --git a/contracts/contracts/coordination/BqETHSubscription.sol b/contracts/contracts/coordination/BqETHSubscription.sol new file mode 100644 index 000000000..c34971197 --- /dev/null +++ b/contracts/contracts/coordination/BqETHSubscription.sol @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "./Coordinator.sol"; +import "./IFeeModel.sol"; + +using SafeERC20 for IERC20; + +/** + * @title BqETH Subscription + * @notice Manages the subscription information for rituals. + */ +contract BqETHSubscription is IFeeModel { + Coordinator public immutable coordinator; + IERC20 public immutable feeToken; + + // TODO: DAO Treasury + // TODO: Should it be updatable? + address public immutable beneficiary; + address public immutable adopter; + + uint256 public immutable feeRate; + uint256 public immutable maxNodes; + uint32 public immutable maxDuration; + + uint32 public endOfSubscription; + uint32 public acttiveRitualId; + + /** + * @notice Emitted when a subscription is spent + * @param beneficiary The address of the beneficiary + * @param amount The amount withdrawn + */ + event WithdrawalToBeneficiary(address indexed beneficiary, uint256 amount); + + /** + * @notice Emitted when a subscription is paid + * @param subscriber The address of the subscriber + * @param amount The amount paid + */ + event SubscriptionPaid(address indexed subscriber, uint256 amount); + + /** + * @notice Sets the coordinator and fee token contracts + * @dev The coordinator and fee token contracts cannot be zero addresses + * @param _coordinator The address of the coordinator contract + * @param _feeToken The address of the fee token contract + * @param _beneficiary The address of the beneficiary + * @param _adopter The address of the adopter + * @param _feeRate Fee rate per node per second + * @param _maxNodes Maximum nodes in the package + * @param _maxDuration Maximum duration of ritual + */ + constructor( + Coordinator _coordinator, + IERC20 _feeToken, + address _beneficiary, + address _adopter, + uint256 _feeRate, + uint256 _maxNodes, + uint32 _maxDuration + ) { + require(address(_coordinator) != address(0), "Coordinator cannot be the zero address"); + require(address(_feeToken) != address(0), "Fee token cannot be the zero address"); + require(_beneficiary != address(0), "Beneficiary cannot be the zero address"); + require(_adopter != address(0), "Adopter cannot be the zero address"); + coordinator = _coordinator; + feeToken = _feeToken; + beneficiary = _beneficiary; + adopter = _adopter; + feeRate = _feeRate; + maxNodes = _maxNodes; + maxDuration = _maxDuration; + } + + modifier onlyBeneficiary() { + require(msg.sender == beneficiary, "Only the beneficiary can call this method"); + _; + } + + modifier onlyAdopter() { + require(msg.sender == beneficiary, "Only the adopter can call this method"); + _; + } + + function packageFees() public view returns (uint256) { + return feeRate * maxDuration * maxNodes; + } + + /** + * @notice Pays for a subscription + */ + function paySubscriptionFor() external { + require(endOfSubscription == 0, "Subscription already payed"); + uint256 amount = packageFees(); + endOfSubscription = uint32(block.timestamp + maxDuration); + + feeToken.safeTransferFrom(msg.sender, address(this), amount); + emit SubscriptionPaid(msg.sender, amount); + } + + /** + * @notice Withdraws the contract balance to the beneficiary + * @param amount The amount to withdraw + */ + function withdrawToBeneficiary(uint256 amount) external onlyBeneficiary { + require(amount <= feeToken.balanceOf(address(this)), "Insufficient available amount"); + feeToken.safeTransfer(beneficiary, amount); + emit WithdrawalToBeneficiary(beneficiary, amount); + } + + function processRitualPayment( + address initiator, + uint32 ritualId, + uint256 numberOfProviders, + uint32 duration + ) external override { + require(initiator == adopter, "Only adopter can initiate ritual"); + require(endOfSubscription != 0, "Subscription has to be payed first"); + require( + endOfSubscription >= block.timestamp + duration && numberOfProviders <= maxNodes, + "Ritual parameters exceed available in package" + ); + if (acttiveRitualId != 0) { + Coordinator.RitualState state = coordinator.getRitualState(ritualId); + require( + state == Coordinator.RitualState.DKG_INVALID || + state == Coordinator.RitualState.DKG_TIMEOUT, + "Only failed rituals allowed to be reinitiate" + ); + } + acttiveRitualId = ritualId; + } + + /** + * @dev This function is called before the setAuthorizations function + * @param ritualId The ID of the ritual + * @param addresses The addresses to be authorized + * @param value The authorization status + */ + function beforeSetAuthorization( + uint32 ritualId, + address[] calldata addresses, + bool value + ) external override { + // solhint-disable-previous-line no-empty-blocks + } +} diff --git a/contracts/contracts/coordination/IFeeModel.sol b/contracts/contracts/coordination/IFeeModel.sol index 5275c7f69..0d54ebaf7 100644 --- a/contracts/contracts/coordination/IFeeModel.sol +++ b/contracts/contracts/coordination/IFeeModel.sol @@ -9,13 +9,6 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; * @notice IFeeModel */ interface IFeeModel { - function currency() external view returns (IERC20); - - function getRitualInitiationCost( - uint256 numberOfProviders, - uint32 duration - ) external view returns (uint256); - function processRitualPayment( address initiator, uint32 ritualId, @@ -33,5 +26,5 @@ interface IFeeModel { uint32 ritualId, address[] calldata addresses, bool value - ) external; + ) external; // TODO consider to move into implementation } diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index 3facc7d53..386ee3cc0 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -117,8 +117,10 @@ def fee_model(project, deployer, coordinator, erc20, treasury): @pytest.fixture() -def global_allow_list(project, deployer, coordinator): - contract = project.GlobalAllowList.deploy(coordinator.address, sender=deployer) +def global_allow_list(project, deployer, coordinator, fee_model): + contract = project.GlobalAllowList.deploy( + coordinator.address, fee_model.address, sender=deployer + ) return contract From 66ced185321c97d46445160200cfc5a393d0dc68 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Wed, 29 May 2024 19:32:01 -0400 Subject: [PATCH 033/105] FeeModel: adds yellow and red period and extending ritual --- .../coordination/BetaProgramInitiator.sol | 4 +- .../coordination/BqETHSubscription.sol | 58 ++++++++++++++----- .../contracts/coordination/Coordinator.sol | 17 ++++++ .../coordination/FlatRateFeeModel.sol | 24 +++++++- .../contracts/coordination/IFeeModel.sol | 9 ++- tests/test_beta_program_initiator.py | 14 ++--- tests/test_coordinator.py | 8 +-- tests/test_global_allow_list.py | 2 +- 8 files changed, 103 insertions(+), 33 deletions(-) diff --git a/contracts/contracts/coordination/BetaProgramInitiator.sol b/contracts/contracts/coordination/BetaProgramInitiator.sol index 1486b2f7f..32733b7b7 100644 --- a/contracts/contracts/coordination/BetaProgramInitiator.sol +++ b/contracts/contracts/coordination/BetaProgramInitiator.sol @@ -69,7 +69,7 @@ contract BetaProgramInitiator { uint32 duration, IEncryptionAuthorizer accessController ) external returns (uint256 requestIndex) { - uint256 ritualCost = feeModel.getRitualInitiationCost(providers.length, duration); + uint256 ritualCost = feeModel.getRitualCost(providers.length, duration); requestIndex = requests.length; InitiationRequest storage request = requests.push(); @@ -122,7 +122,7 @@ contract BetaProgramInitiator { address[] memory providers = request.providers; uint32 duration = request.duration; - uint256 ritualCost = feeModel.getRitualInitiationCost(providers.length, duration); + uint256 ritualCost = feeModel.getRitualCost(providers.length, duration); require(ritualCost == request.payment, "Ritual initiation cost has changed"); currency.approve(address(feeModel), ritualCost); diff --git a/contracts/contracts/coordination/BqETHSubscription.sol b/contracts/contracts/coordination/BqETHSubscription.sol index c34971197..e80a9a83b 100644 --- a/contracts/contracts/coordination/BqETHSubscription.sol +++ b/contracts/contracts/coordination/BqETHSubscription.sol @@ -25,6 +25,8 @@ contract BqETHSubscription is IFeeModel { uint256 public immutable feeRate; uint256 public immutable maxNodes; uint32 public immutable maxDuration; + uint32 public immutable yellowPeriodDuration; + uint32 public immutable redPeriodDuration; uint32 public endOfSubscription; uint32 public acttiveRitualId; @@ -53,6 +55,8 @@ contract BqETHSubscription is IFeeModel { * @param _feeRate Fee rate per node per second * @param _maxNodes Maximum nodes in the package * @param _maxDuration Maximum duration of ritual + * @param _yellowPeriodDuration Duration of yellow period + * @param _redPeriodDuration Duration of red period */ constructor( Coordinator _coordinator, @@ -61,7 +65,9 @@ contract BqETHSubscription is IFeeModel { address _adopter, uint256 _feeRate, uint256 _maxNodes, - uint32 _maxDuration + uint32 _maxDuration, + uint32 _yellowPeriodDuration, + uint32 _redPeriodDuration ) { require(address(_coordinator) != address(0), "Coordinator cannot be the zero address"); require(address(_feeToken) != address(0), "Fee token cannot be the zero address"); @@ -74,6 +80,13 @@ contract BqETHSubscription is IFeeModel { feeRate = _feeRate; maxNodes = _maxNodes; maxDuration = _maxDuration; + yellowPeriodDuration = _yellowPeriodDuration; + redPeriodDuration = _redPeriodDuration; + } + + modifier onlyCoordinator() { + require(msg.sender == beneficiary, "Only the Coordinator can call this method"); + _; } modifier onlyBeneficiary() { @@ -94,9 +107,12 @@ contract BqETHSubscription is IFeeModel { * @notice Pays for a subscription */ function paySubscriptionFor() external { - require(endOfSubscription == 0, "Subscription already payed"); + // require(endOfSubscription == 0, "Subscription already payed"); uint256 amount = packageFees(); - endOfSubscription = uint32(block.timestamp + maxDuration); + if (endOfSubscription == 0) { + endOfSubscription = uint32(block.timestamp); + } + endOfSubscription += uint32(maxDuration); feeToken.safeTransferFrom(msg.sender, address(this), amount); emit SubscriptionPaid(msg.sender, amount); @@ -117,35 +133,47 @@ contract BqETHSubscription is IFeeModel { uint32 ritualId, uint256 numberOfProviders, uint32 duration - ) external override { + ) external override onlyCoordinator { require(initiator == adopter, "Only adopter can initiate ritual"); require(endOfSubscription != 0, "Subscription has to be payed first"); require( - endOfSubscription >= block.timestamp + duration && numberOfProviders <= maxNodes, + endOfSubscription + yellowPeriodDuration + redPeriodDuration >= + block.timestamp + duration && + numberOfProviders <= maxNodes, "Ritual parameters exceed available in package" ); if (acttiveRitualId != 0) { Coordinator.RitualState state = coordinator.getRitualState(ritualId); require( state == Coordinator.RitualState.DKG_INVALID || - state == Coordinator.RitualState.DKG_TIMEOUT, + state == Coordinator.RitualState.DKG_TIMEOUT || + state == Coordinator.RitualState.EXPIRED, // TODO check if it's ok "Only failed rituals allowed to be reinitiate" ); } acttiveRitualId = ritualId; } + function processRitualExtending( + address, + uint32 ritualId, + uint256, + uint32 + ) external view override onlyCoordinator { + (, uint32 endTimestamp) = coordinator.getTimestamps(ritualId); + require( + endOfSubscription + yellowPeriodDuration + redPeriodDuration >= endTimestamp, + "Ritual parameters exceed available in package" + ); + } + /** * @dev This function is called before the setAuthorizations function - * @param ritualId The ID of the ritual - * @param addresses The addresses to be authorized - * @param value The authorization status */ - function beforeSetAuthorization( - uint32 ritualId, - address[] calldata addresses, - bool value - ) external override { - // solhint-disable-previous-line no-empty-blocks + function beforeSetAuthorization(uint32, address[] calldata, bool) external view override { + require( + block.timestamp <= endOfSubscription + yellowPeriodDuration, + "Yellow period of subscription has expired" + ); } } diff --git a/contracts/contracts/coordination/Coordinator.sol b/contracts/contracts/coordination/Coordinator.sol index 2326ae79d..b5f34a4ca 100644 --- a/contracts/contracts/coordination/Coordinator.sol +++ b/contracts/contracts/coordination/Coordinator.sol @@ -43,6 +43,7 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable BLS12381.G2Point publicKey ); event FeeModelApproved(IFeeModel feeModel); + event RitualExtended(uint32 indexed ritualId, uint32 indexed duration); enum RitualState { NON_INITIATED, @@ -79,6 +80,7 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable BLS12381.G1Point publicKey; bytes aggregatedTranscript; Participant[] participant; + IFeeModel feeModel; } struct ParticipantKey { @@ -291,6 +293,7 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable ritual.initTimestamp = uint32(block.timestamp); ritual.endTimestamp = ritual.initTimestamp + duration; ritual.accessController = accessController; + ritual.feeModel = feeModel; address previous = address(0); for (uint256 i = 0; i < length; i++) { @@ -557,4 +560,18 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable feeModelsRegistry[feeModel] = true; emit FeeModelApproved(feeModel); } + + function extendRitual(uint32 ritualId, uint32 duration) external { + Ritual storage ritual = rituals[ritualId]; + require(msg.sender == ritual.initiator, "Only initiator can extend ritual"); + require(getRitualState(ritual) == RitualState.ACTIVE, "Only active ritual can be extended"); + ritual.endTimestamp += duration; + ritual.feeModel.processRitualExtending( + ritual.initiator, + ritualId, + ritual.participant.length, + duration + ); + emit RitualExtended(ritualId, duration); + } } diff --git a/contracts/contracts/coordination/FlatRateFeeModel.sol b/contracts/contracts/coordination/FlatRateFeeModel.sol index a00324af0..d97bd5417 100644 --- a/contracts/contracts/coordination/FlatRateFeeModel.sol +++ b/contracts/contracts/coordination/FlatRateFeeModel.sol @@ -33,7 +33,7 @@ contract FlatRateFeeModel is IFeeModel, Ownable { coordinator = _coordinator; } - function getRitualInitiationCost( + function getRitualCost( uint256 numberOfProviders, uint32 duration ) public view returns (uint256) { @@ -53,11 +53,29 @@ contract FlatRateFeeModel is IFeeModel, Ownable { uint256 numberOfProviders, uint32 duration ) external override { + processPayment(initiator, ritualId, numberOfProviders, duration); + } + + function processRitualExtending( + address initiator, + uint32 ritualId, + uint256 numberOfProviders, + uint32 duration + ) external override { + processPayment(initiator, ritualId, numberOfProviders, duration); + } + + function processPayment( + address initiator, + uint32 ritualId, + uint256 numberOfProviders, + uint32 duration + ) internal { require(msg.sender == address(coordinator), "Only coordinator can call process payment"); - uint256 ritualCost = getRitualInitiationCost(numberOfProviders, duration); + uint256 ritualCost = getRitualCost(numberOfProviders, duration); require(ritualCost > 0, "Invalid ritual cost"); totalPendingFees += ritualCost; - pendingFees[ritualId] = ritualCost; + pendingFees[ritualId] += ritualCost; currency.safeTransferFrom(initiator, address(this), ritualCost); } diff --git a/contracts/contracts/coordination/IFeeModel.sol b/contracts/contracts/coordination/IFeeModel.sol index 0d54ebaf7..af2d42e74 100644 --- a/contracts/contracts/coordination/IFeeModel.sol +++ b/contracts/contracts/coordination/IFeeModel.sol @@ -16,6 +16,13 @@ interface IFeeModel { uint32 duration ) external; + function processRitualExtending( + address initiator, + uint32 ritualId, + uint256 numberOfProviders, + uint32 duration + ) external; + /** * @dev This function is called before the setAuthorizations function * @param ritualId The ID of the ritual @@ -26,5 +33,5 @@ interface IFeeModel { uint32 ritualId, address[] calldata addresses, bool value - ) external; // TODO consider to move into implementation + ) external; } diff --git a/tests/test_beta_program_initiator.py b/tests/test_beta_program_initiator.py index c59c02a33..b7c4fa735 100644 --- a/tests/test_beta_program_initiator.py +++ b/tests/test_beta_program_initiator.py @@ -91,7 +91,7 @@ def test_register(accounts, beta_program_initiator, token, fee_model): nodes = [node_1, node_2] duration = DAY_IN_SECONDS - ritual_cost = fee_model.getRitualInitiationCost(len(nodes), duration) + ritual_cost = fee_model.getRitualCost(len(nodes), duration) # Can't register request without token transfer approval with ape.reverts(): @@ -130,7 +130,7 @@ def test_register(accounts, beta_program_initiator, token, fee_model): # Register another request nodes = [node_1] duration = 3 * DAY_IN_SECONDS - ritual_cost_2 = fee_model.getRitualInitiationCost(len(nodes), duration) + ritual_cost_2 = fee_model.getRitualCost(len(nodes), duration) token.mint(initiator_2, ritual_cost_2, sender=initiator_2) token.approve(beta_program_initiator.address, ritual_cost_2, sender=initiator_2) @@ -173,7 +173,7 @@ def test_cancel(accounts, beta_program_initiator, token, executor, fee_model): nodes = [node_1, node_2] duration = DAY_IN_SECONDS - ritual_cost = fee_model.getRitualInitiationCost(len(nodes), duration) + ritual_cost = fee_model.getRitualCost(len(nodes), duration) token.mint(initiator_1, 10 * ritual_cost, sender=initiator_1) token.approve(beta_program_initiator.address, 10 * ritual_cost, sender=initiator_1) @@ -249,10 +249,10 @@ def test_execute(accounts, beta_program_initiator, token, coordinator, executor, nodes_1 = [node_1, node_2] duration_1 = DAY_IN_SECONDS - ritual_cost_1 = fee_model.getRitualInitiationCost(len(nodes_1), duration_1) + ritual_cost_1 = fee_model.getRitualCost(len(nodes_1), duration_1) nodes_2 = [node_1] duration_2 = 2 * duration_1 - ritual_cost_2 = fee_model.getRitualInitiationCost(len(nodes_2), duration_2) + ritual_cost_2 = fee_model.getRitualCost(len(nodes_2), duration_2) token.mint(initiator_1, 10 * ritual_cost_1, sender=initiator_1) token.approve(beta_program_initiator.address, 10 * ritual_cost_1, sender=initiator_1) @@ -357,9 +357,9 @@ def test_refund(accounts, beta_program_initiator, token, coordinator, executor, nodes = [node_1, node_2] duration_1 = DAY_IN_SECONDS - ritual_cost_1 = fee_model.getRitualInitiationCost(len(nodes), duration_1) + ritual_cost_1 = fee_model.getRitualCost(len(nodes), duration_1) duration_2 = 3 * duration_1 - ritual_cost_2 = fee_model.getRitualInitiationCost(len(nodes), duration_2) + ritual_cost_2 = fee_model.getRitualCost(len(nodes), duration_2) token.mint(initiator_1, 10 * ritual_cost_1, sender=initiator_1) token.approve(beta_program_initiator.address, 10 * ritual_cost_1, sender=initiator_1) diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index 386ee3cc0..e8ce21ddf 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -197,7 +197,7 @@ def initiate_ritual(coordinator, fee_model, erc20, authority, nodes, allow_logic coordinator.setProviderPublicKey(public_key, sender=node) assert coordinator.isProviderPublicKeySet(node) - cost = fee_model.getRitualInitiationCost(len(nodes), DURATION) + cost = fee_model.getRitualCost(len(nodes), DURATION) erc20.approve(fee_model.address, cost, sender=authority) tx = coordinator.initiateRitual( fee_model, nodes, authority, DURATION, allow_logic.address, sender=authority @@ -241,7 +241,7 @@ def test_initiate_ritual( assert ritual_struct[10] == (b"\x00" * 32, b"\x00" * 16) # publicKey assert not ritual_struct[11] # aggregatedTranscript - fee = fee_model.getRitualInitiationCost(len(nodes), DURATION) + fee = fee_model.getRitualCost(len(nodes), DURATION) assert erc20.balanceOf(fee_model) == fee assert fee_model.totalPendingFees() == fee assert fee_model.pendingFees(ritualID) == fee @@ -503,7 +503,7 @@ def test_post_aggregation( assert coordinator.getRitualIdFromPublicKey(dkg_public_key) == ritualID fee_model.processPendingFee(ritualID, sender=treasury) - fee = fee_model.getRitualInitiationCost(len(nodes), DURATION) + fee = fee_model.getRitualCost(len(nodes), DURATION) assert erc20.balanceOf(fee_model) == fee assert fee_model.totalPendingFees() == 0 assert fee_model.pendingFees(ritualID) == 0 @@ -551,7 +551,7 @@ def test_post_aggregation_fails( assert events == [coordinator.EndRitual(ritualId=ritualID, successful=False)] # Fees are still pending - fee = fee_model.getRitualInitiationCost(len(nodes), DURATION) + fee = fee_model.getRitualCost(len(nodes), DURATION) assert erc20.balanceOf(fee_model) == fee assert fee_model.totalPendingFees() == fee pending_fee = fee_model.pendingFees(ritualID) diff --git a/tests/test_global_allow_list.py b/tests/test_global_allow_list.py index 4d374c169..72a8a0a53 100644 --- a/tests/test_global_allow_list.py +++ b/tests/test_global_allow_list.py @@ -130,7 +130,7 @@ def initiate_ritual(coordinator, fee_model, erc20, authority, nodes, allow_logic coordinator.setProviderPublicKey(public_key, sender=node) assert coordinator.isProviderPublicKeySet(node) - cost = fee_model.getRitualInitiationCost(len(nodes), DURATION) + cost = fee_model.getRitualCost(len(nodes), DURATION) erc20.approve(fee_model.address, cost, sender=authority) tx = coordinator.initiateRitual( fee_model, nodes, authority, DURATION, allow_logic.address, sender=authority From cc6cedcbfc51cfa01647ed5ed8212159607eb6fa Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Fri, 31 May 2024 14:06:49 -0400 Subject: [PATCH 034/105] Check access controller in subscription --- .../contracts/coordination/BqETHSubscription.sol | 16 +++++++++++++++- contracts/contracts/coordination/Coordinator.sol | 4 ++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/contracts/contracts/coordination/BqETHSubscription.sol b/contracts/contracts/coordination/BqETHSubscription.sol index e80a9a83b..592474044 100644 --- a/contracts/contracts/coordination/BqETHSubscription.sol +++ b/contracts/contracts/coordination/BqETHSubscription.sol @@ -28,6 +28,8 @@ contract BqETHSubscription is IFeeModel { uint32 public immutable yellowPeriodDuration; uint32 public immutable redPeriodDuration; + IEncryptionAuthorizer public immutable accessController; + uint32 public endOfSubscription; uint32 public acttiveRitualId; @@ -57,6 +59,7 @@ contract BqETHSubscription is IFeeModel { * @param _maxDuration Maximum duration of ritual * @param _yellowPeriodDuration Duration of yellow period * @param _redPeriodDuration Duration of red period + * @param _accessController Address of allowed access controller */ constructor( Coordinator _coordinator, @@ -67,12 +70,17 @@ contract BqETHSubscription is IFeeModel { uint256 _maxNodes, uint32 _maxDuration, uint32 _yellowPeriodDuration, - uint32 _redPeriodDuration + uint32 _redPeriodDuration, + IEncryptionAuthorizer _accessController ) { require(address(_coordinator) != address(0), "Coordinator cannot be the zero address"); require(address(_feeToken) != address(0), "Fee token cannot be the zero address"); require(_beneficiary != address(0), "Beneficiary cannot be the zero address"); require(_adopter != address(0), "Adopter cannot be the zero address"); + require( + address(_accessController) != address(0), + "Access controller cannot be the zero address" + ); coordinator = _coordinator; feeToken = _feeToken; beneficiary = _beneficiary; @@ -82,6 +90,7 @@ contract BqETHSubscription is IFeeModel { maxDuration = _maxDuration; yellowPeriodDuration = _yellowPeriodDuration; redPeriodDuration = _redPeriodDuration; + accessController = _accessController; } modifier onlyCoordinator() { @@ -142,6 +151,11 @@ contract BqETHSubscription is IFeeModel { numberOfProviders <= maxNodes, "Ritual parameters exceed available in package" ); + require( + accessController == coordinator.getAccessController(ritualId), + "Access controller ofr ritual must be approved" + ); + if (acttiveRitualId != 0) { Coordinator.RitualState state = coordinator.getRitualState(ritualId); require( diff --git a/contracts/contracts/coordination/Coordinator.sol b/contracts/contracts/coordination/Coordinator.sol index b5f34a4ca..83a237dce 100644 --- a/contracts/contracts/coordination/Coordinator.sol +++ b/contracts/contracts/coordination/Coordinator.sol @@ -139,6 +139,10 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable endTimestamp = rituals[ritualId].endTimestamp; } + function getAccessController(uint32 ritualId) external view returns (IEncryptionAuthorizer) { + return rituals[ritualId].accessController; + } + function getRitualState(uint32 ritualId) external view returns (RitualState) { return getRitualState(rituals[ritualId]); } From 1f03a820dd87e72062a624525a2947c2d787049f Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Tue, 4 Jun 2024 15:06:43 -0400 Subject: [PATCH 035/105] BqETHSubscription: fix bugs and add tests --- .../coordination/BqETHSubscription.sol | 65 ++-- .../test/BetaProgramInitiatorTestSet.sol | 4 - contracts/test/BqETHSubscriptionTestSet.sol | 81 ++++ tests/test_bqeth_subscription.py | 365 ++++++++++++++++++ 4 files changed, 487 insertions(+), 28 deletions(-) create mode 100644 contracts/test/BqETHSubscriptionTestSet.sol create mode 100644 tests/test_bqeth_subscription.py diff --git a/contracts/contracts/coordination/BqETHSubscription.sol b/contracts/contracts/coordination/BqETHSubscription.sol index 592474044..7996c0086 100644 --- a/contracts/contracts/coordination/BqETHSubscription.sol +++ b/contracts/contracts/coordination/BqETHSubscription.sol @@ -17,6 +17,8 @@ contract BqETHSubscription is IFeeModel { Coordinator public immutable coordinator; IERC20 public immutable feeToken; + uint32 public constant INACTIVE_RITUAL_ID = type(uint32).max; + // TODO: DAO Treasury // TODO: Should it be updatable? address public immutable beneficiary; @@ -28,10 +30,10 @@ contract BqETHSubscription is IFeeModel { uint32 public immutable yellowPeriodDuration; uint32 public immutable redPeriodDuration; - IEncryptionAuthorizer public immutable accessController; + IEncryptionAuthorizer public accessController; uint32 public endOfSubscription; - uint32 public acttiveRitualId; + uint32 public activeRitualId; /** * @notice Emitted when a subscription is spent @@ -59,7 +61,6 @@ contract BqETHSubscription is IFeeModel { * @param _maxDuration Maximum duration of ritual * @param _yellowPeriodDuration Duration of yellow period * @param _redPeriodDuration Duration of red period - * @param _accessController Address of allowed access controller */ constructor( Coordinator _coordinator, @@ -70,17 +71,12 @@ contract BqETHSubscription is IFeeModel { uint256 _maxNodes, uint32 _maxDuration, uint32 _yellowPeriodDuration, - uint32 _redPeriodDuration, - IEncryptionAuthorizer _accessController + uint32 _redPeriodDuration ) { require(address(_coordinator) != address(0), "Coordinator cannot be the zero address"); require(address(_feeToken) != address(0), "Fee token cannot be the zero address"); require(_beneficiary != address(0), "Beneficiary cannot be the zero address"); require(_adopter != address(0), "Adopter cannot be the zero address"); - require( - address(_accessController) != address(0), - "Access controller cannot be the zero address" - ); coordinator = _coordinator; feeToken = _feeToken; beneficiary = _beneficiary; @@ -90,11 +86,10 @@ contract BqETHSubscription is IFeeModel { maxDuration = _maxDuration; yellowPeriodDuration = _yellowPeriodDuration; redPeriodDuration = _redPeriodDuration; - accessController = _accessController; } modifier onlyCoordinator() { - require(msg.sender == beneficiary, "Only the Coordinator can call this method"); + require(msg.sender == address(coordinator), "Only the Coordinator can call this method"); _; } @@ -103,11 +98,31 @@ contract BqETHSubscription is IFeeModel { _; } - modifier onlyAdopter() { - require(msg.sender == beneficiary, "Only the adopter can call this method"); + modifier onlyAccessController() { + require( + msg.sender == address(accessController), + "Only Access Controller can call this method" + ); _; } + modifier onlyActiveRitual(uint32 ritualId) { + require( + activeRitualId != INACTIVE_RITUAL_ID && ritualId == activeRitualId, + "Ritual must be active" + ); + _; + } + + function initialize(IEncryptionAuthorizer _accessController) external { + require( + address(accessController) == address(0) && address(_accessController) != address(0), + "Access controller cannot be the zero address" + ); + accessController = _accessController; + activeRitualId = INACTIVE_RITUAL_ID; + } + function packageFees() public view returns (uint256) { return feeRate * maxDuration * maxNodes; } @@ -152,12 +167,13 @@ contract BqETHSubscription is IFeeModel { "Ritual parameters exceed available in package" ); require( - accessController == coordinator.getAccessController(ritualId), - "Access controller ofr ritual must be approved" + address(accessController) != address(0) && + accessController == coordinator.getAccessController(ritualId), + "Access controller for ritual must be approved" ); - if (acttiveRitualId != 0) { - Coordinator.RitualState state = coordinator.getRitualState(ritualId); + if (activeRitualId != INACTIVE_RITUAL_ID) { + Coordinator.RitualState state = coordinator.getRitualState(activeRitualId); require( state == Coordinator.RitualState.DKG_INVALID || state == Coordinator.RitualState.DKG_TIMEOUT || @@ -165,7 +181,7 @@ contract BqETHSubscription is IFeeModel { "Only failed rituals allowed to be reinitiate" ); } - acttiveRitualId = ritualId; + activeRitualId = ritualId; } function processRitualExtending( @@ -173,7 +189,7 @@ contract BqETHSubscription is IFeeModel { uint32 ritualId, uint256, uint32 - ) external view override onlyCoordinator { + ) external view override onlyCoordinator onlyActiveRitual(ritualId) { (, uint32 endTimestamp) = coordinator.getTimestamps(ritualId); require( endOfSubscription + yellowPeriodDuration + redPeriodDuration >= endTimestamp, @@ -184,10 +200,11 @@ contract BqETHSubscription is IFeeModel { /** * @dev This function is called before the setAuthorizations function */ - function beforeSetAuthorization(uint32, address[] calldata, bool) external view override { - require( - block.timestamp <= endOfSubscription + yellowPeriodDuration, - "Yellow period of subscription has expired" - ); + function beforeSetAuthorization( + uint32 ritualId, + address[] calldata, + bool + ) external view override onlyAccessController onlyActiveRitual(ritualId) { + require(block.timestamp <= endOfSubscription, "Subscription has expired"); } } diff --git a/contracts/test/BetaProgramInitiatorTestSet.sol b/contracts/test/BetaProgramInitiatorTestSet.sol index 838f08e30..91cdc17f8 100644 --- a/contracts/test/BetaProgramInitiatorTestSet.sol +++ b/contracts/test/BetaProgramInitiatorTestSet.sol @@ -2,15 +2,11 @@ pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "../contracts/coordination/IEncryptionAuthorizer.sol"; import "../contracts/coordination/Coordinator.sol"; import "../contracts/coordination/IFeeModel.sol"; contract CoordinatorForBetaProgramInitiatorMock { - using SafeERC20 for IERC20; - struct Ritual { address initiator; address[] providers; diff --git a/contracts/test/BqETHSubscriptionTestSet.sol b/contracts/test/BqETHSubscriptionTestSet.sol new file mode 100644 index 000000000..a31883a8d --- /dev/null +++ b/contracts/test/BqETHSubscriptionTestSet.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.0; + +import "../contracts/coordination/IEncryptionAuthorizer.sol"; +import "../contracts/coordination/Coordinator.sol"; +import "../contracts/coordination/IFeeModel.sol"; + +contract CoordinatorForBqETHSubscriptionMock { + struct Ritual { + uint32 endTimestamp; + IEncryptionAuthorizer accessController; + Coordinator.RitualState state; + } + + IFeeModel public feeModel; + + mapping(uint32 => Ritual) public rituals; + + function setFeeModel(IFeeModel _feeModel) external { + feeModel = _feeModel; + } + + function processRitualExtending( + address initiator, + uint32 ritualId, + uint256 numberOfProviders, + uint32 duration + ) external { + feeModel.processRitualExtending(initiator, ritualId, numberOfProviders, duration); + } + + function processRitualPayment( + address initiator, + uint32 ritualId, + uint256 numberOfProviders, + uint32 duration + ) external { + feeModel.processRitualPayment(initiator, ritualId, numberOfProviders, duration); + } + + function setRitual( + uint32 _ritualId, + Coordinator.RitualState _state, + uint32 _endTimestamp, + IEncryptionAuthorizer _accessController + ) external { + Ritual storage ritual = rituals[_ritualId]; + ritual.state = _state; + ritual.endTimestamp = _endTimestamp; + ritual.accessController = _accessController; + } + + function getAccessController(uint32 _ritualId) external view returns (IEncryptionAuthorizer) { + return rituals[_ritualId].accessController; + } + + function getRitualState(uint32 _ritualId) external view returns (Coordinator.RitualState) { + return rituals[_ritualId].state; + } + + function getTimestamps( + uint32 _ritualId + ) external view returns (uint32 initTimestamp, uint32 endTimestamp) { + initTimestamp = 0; + endTimestamp = rituals[_ritualId].endTimestamp; + } + + function numberOfRituals() external pure returns (uint256) { + return 1; + } + + function getAuthority(uint32) external view returns (address) { + // solhint-disable-next-line avoid-tx-origin + return tx.origin; + } + + function isRitualActive(uint32) external view returns (bool) { + return true; + } +} diff --git a/tests/test_bqeth_subscription.py b/tests/test_bqeth_subscription.py new file mode 100644 index 000000000..b7e258aba --- /dev/null +++ b/tests/test_bqeth_subscription.py @@ -0,0 +1,365 @@ +""" +This file is part of nucypher. + +nucypher is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +nucypher is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with nucypher. If not, see . +""" +from enum import IntEnum + +import ape +import pytest + +FEE_RATE = 42 +MAX_NODES = 10 + + +ERC20_SUPPLY = 10**24 +DURATION = 48 * 60 * 60 +ONE_DAY = 24 * 60 * 60 + +MAX_DURATION = 3 * DURATION +YELLOW_PERIOD = ONE_DAY +RED_PERIOD = 5 * ONE_DAY + +FEE = FEE_RATE * MAX_DURATION * MAX_NODES + +RitualState = IntEnum( + "RitualState", + [ + "NON_INITIATED", + "DKG_AWAITING_TRANSCRIPTS", + "DKG_AWAITING_AGGREGATIONS", + "DKG_TIMEOUT", + "DKG_INVALID", + "ACTIVE", + "EXPIRED", + ], + start=0, +) + + +@pytest.fixture(scope="module") +def treasury(accounts): + return accounts[1] + + +@pytest.fixture(scope="module") +def adopter(accounts): + return accounts[2] + + +@pytest.fixture() +def erc20(project, adopter): + token = project.TestToken.deploy(ERC20_SUPPLY, sender=adopter) + return token + + +@pytest.fixture() +def coordinator(project, creator): + contract = project.CoordinatorForBqETHSubscriptionMock.deploy( + sender=creator, + ) + return contract + + +@pytest.fixture() +def subscription(project, creator, coordinator, erc20, treasury, adopter): + contract = project.BqETHSubscription.deploy( + coordinator.address, + erc20.address, + treasury, + adopter, + FEE_RATE, + MAX_NODES, + MAX_DURATION, + YELLOW_PERIOD, + RED_PERIOD, + sender=creator, + ) + coordinator.setFeeModel(contract.address, sender=creator) + return contract + + +@pytest.fixture() +def global_allow_list(project, creator, coordinator, subscription, treasury): + contract = project.GlobalAllowList.deploy( + coordinator.address, subscription.address, sender=creator + ) + subscription.initialize(contract.address, sender=treasury) + return contract + + +def test_pay_subscription(erc20, subscription, adopter, chain): + erc20.approve(subscription.address, 10 * FEE, sender=adopter) + + # First payment + balance_before = erc20.balanceOf(adopter) + assert subscription.packageFees() == FEE + + tx = subscription.paySubscriptionFor(sender=adopter) + timestamp = chain.pending_timestamp - 1 + assert subscription.endOfSubscription() == timestamp + MAX_DURATION + balance_after = erc20.balanceOf(adopter) + assert balance_after + FEE == balance_before + assert erc20.balanceOf(subscription.address) == FEE + + events = subscription.SubscriptionPaid.from_receipt(tx) + assert events == [subscription.SubscriptionPaid(subscriber=adopter, amount=FEE)] + + # Top up + balance_before = erc20.balanceOf(adopter) + tx = subscription.paySubscriptionFor(sender=adopter) + assert subscription.endOfSubscription() == timestamp + 2 * MAX_DURATION + balance_after = erc20.balanceOf(adopter) + assert balance_after + FEE == balance_before + assert erc20.balanceOf(subscription.address) == 2 * FEE + + events = subscription.SubscriptionPaid.from_receipt(tx) + assert events == [subscription.SubscriptionPaid(subscriber=adopter, amount=FEE)] + + +def test_withdraw(erc20, subscription, adopter, treasury): + erc20.approve(subscription.address, 10 * FEE, sender=adopter) + + with ape.reverts("Only the beneficiary can call this method"): + subscription.withdrawToBeneficiary(1, sender=adopter) + + with ape.reverts("Insufficient available amount"): + subscription.withdrawToBeneficiary(1, sender=treasury) + + subscription.paySubscriptionFor(sender=adopter) + + with ape.reverts("Insufficient available amount"): + subscription.withdrawToBeneficiary(FEE + 1, sender=treasury) + + tx = subscription.withdrawToBeneficiary(FEE, sender=treasury) + assert erc20.balanceOf(treasury) == FEE + assert erc20.balanceOf(subscription.address) == 0 + + events = subscription.WithdrawalToBeneficiary.from_receipt(tx) + assert events == [subscription.WithdrawalToBeneficiary(beneficiary=treasury, amount=FEE)] + + +def test_process_ritual_payment( + erc20, subscription, coordinator, global_allow_list, adopter, treasury +): + ritual_id = 7 + number_of_providers = 6 + + with ape.reverts("Only the Coordinator can call this method"): + subscription.processRitualPayment( + adopter, ritual_id, number_of_providers, DURATION, sender=treasury + ) + with ape.reverts("Only adopter can initiate ritual"): + coordinator.processRitualPayment( + treasury, ritual_id, number_of_providers, DURATION, sender=treasury + ) + with ape.reverts("Subscription has to be payed first"): + coordinator.processRitualPayment( + adopter, ritual_id, number_of_providers, DURATION, sender=treasury + ) + + erc20.approve(subscription.address, 10 * FEE, sender=adopter) + subscription.paySubscriptionFor(sender=adopter) + + with ape.reverts("Ritual parameters exceed available in package"): + coordinator.processRitualPayment( + adopter, ritual_id, MAX_NODES + 1, DURATION, sender=treasury + ) + with ape.reverts("Ritual parameters exceed available in package"): + coordinator.processRitualPayment( + adopter, + ritual_id, + number_of_providers, + MAX_DURATION + YELLOW_PERIOD + RED_PERIOD + 1, + sender=treasury, + ) + + coordinator.setRitual(ritual_id, RitualState.NON_INITIATED, 0, treasury, sender=treasury) + + with ape.reverts("Access controller for ritual must be approved"): + coordinator.processRitualPayment( + adopter, + ritual_id, + MAX_NODES, + MAX_DURATION + YELLOW_PERIOD + RED_PERIOD - 4, + sender=treasury, + ) + + assert subscription.activeRitualId() == subscription.INACTIVE_RITUAL_ID() + coordinator.setRitual( + ritual_id, + RitualState.DKG_AWAITING_TRANSCRIPTS, + 0, + global_allow_list.address, + sender=treasury, + ) + coordinator.processRitualPayment( + adopter, ritual_id, number_of_providers, DURATION, sender=treasury + ) + assert subscription.activeRitualId() == ritual_id + + new_ritual_id = ritual_id + 1 + coordinator.setRitual( + new_ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury + ) + with ape.reverts("Only failed rituals allowed to be reinitiate"): + coordinator.processRitualPayment( + adopter, new_ritual_id, number_of_providers, DURATION, sender=treasury + ) + + coordinator.setRitual( + ritual_id, RitualState.DKG_INVALID, 0, global_allow_list.address, sender=treasury + ) + coordinator.processRitualPayment( + adopter, new_ritual_id, number_of_providers, DURATION, sender=treasury + ) + assert subscription.activeRitualId() == new_ritual_id + + ritual_id = new_ritual_id + new_ritual_id = ritual_id + 1 + coordinator.setRitual( + new_ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury + ) + coordinator.setRitual( + ritual_id, RitualState.DKG_TIMEOUT, 0, global_allow_list.address, sender=treasury + ) + coordinator.processRitualPayment( + adopter, new_ritual_id, number_of_providers, DURATION, sender=treasury + ) + assert subscription.activeRitualId() == new_ritual_id + + ritual_id = new_ritual_id + new_ritual_id = ritual_id + 1 + coordinator.setRitual( + new_ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury + ) + coordinator.setRitual( + ritual_id, RitualState.EXPIRED, 0, global_allow_list.address, sender=treasury + ) + coordinator.processRitualPayment( + adopter, new_ritual_id, number_of_providers, DURATION, sender=treasury + ) + assert subscription.activeRitualId() == new_ritual_id + + +def test_process_ritual_extending( + erc20, subscription, coordinator, adopter, global_allow_list, treasury +): + ritual_id = 6 + number_of_providers = 7 + + with ape.reverts("Only the Coordinator can call this method"): + subscription.processRitualExtending( + adopter, ritual_id, number_of_providers, DURATION, sender=treasury + ) + with ape.reverts("Ritual must be active"): + coordinator.processRitualExtending( + treasury, ritual_id, number_of_providers, DURATION, sender=treasury + ) + + erc20.approve(subscription.address, 10 * FEE, sender=adopter) + subscription.paySubscriptionFor(sender=adopter) + coordinator.setRitual( + ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury + ) + coordinator.processRitualPayment( + adopter, ritual_id, number_of_providers, DURATION, sender=treasury + ) + end_subscription = subscription.endOfSubscription() + max_end_timestamp = end_subscription + YELLOW_PERIOD + RED_PERIOD + + new_ritual_id = ritual_id + 1 + with ape.reverts("Ritual must be active"): + coordinator.processRitualExtending( + treasury, new_ritual_id, number_of_providers, DURATION, sender=treasury + ) + + coordinator.setRitual( + ritual_id, + RitualState.DKG_INVALID, + max_end_timestamp + 1, + global_allow_list.address, + sender=treasury, + ) + + with ape.reverts("Ritual parameters exceed available in package"): + coordinator.processRitualExtending( + treasury, ritual_id, number_of_providers, DURATION, sender=treasury + ) + + coordinator.setRitual( + ritual_id, + RitualState.DKG_INVALID, + max_end_timestamp, + global_allow_list.address, + sender=treasury, + ) + coordinator.processRitualExtending( + adopter, ritual_id, number_of_providers, DURATION, sender=treasury + ) + + coordinator.setRitual( + new_ritual_id, + RitualState.DKG_INVALID, + max_end_timestamp, + global_allow_list.address, + sender=treasury, + ) + coordinator.processRitualPayment( + adopter, new_ritual_id, number_of_providers, DURATION, sender=treasury + ) + with ape.reverts("Ritual must be active"): + coordinator.processRitualExtending( + treasury, ritual_id, number_of_providers, DURATION, sender=treasury + ) + coordinator.processRitualPayment( + adopter, new_ritual_id, number_of_providers, DURATION, sender=treasury + ) + + +def test_before_set_authorization( + erc20, subscription, coordinator, adopter, global_allow_list, treasury, creator, chain +): + ritual_id = 6 + number_of_providers = 7 + + assert subscription.address == global_allow_list.feeModel() + + with ape.reverts("Only Access Controller can call this method"): + subscription.beforeSetAuthorization(0, [creator.address], True, sender=adopter) + + with ape.reverts("Ritual must be active"): + global_allow_list.authorize(0, [creator.address], sender=adopter) + + erc20.approve(subscription.address, 10 * FEE, sender=adopter) + subscription.paySubscriptionFor(sender=adopter) + coordinator.setRitual( + ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury + ) + coordinator.processRitualPayment( + adopter, ritual_id, number_of_providers, DURATION, sender=treasury + ) + + with ape.reverts("Ritual must be active"): + global_allow_list.authorize(0, [creator.address], sender=adopter) + global_allow_list.authorize(ritual_id, [creator.address], sender=adopter) + + end_subscription = subscription.endOfSubscription() + chain.pending_timestamp = end_subscription + 1 + + with ape.reverts("Subscription has expired"): + global_allow_list.authorize(ritual_id, [creator.address], sender=adopter) + + subscription.paySubscriptionFor(sender=adopter) + global_allow_list.authorize(ritual_id, [creator.address], sender=adopter) From 48eed6192591f80d56568f5f196444904d52ab8b Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Wed, 5 Jun 2024 13:31:14 -0400 Subject: [PATCH 036/105] BqETHSubscription: handling yellow period --- .../coordination/BqETHSubscription.sol | 17 ++++++- .../coordination/FlatRateFeeModel.sol | 8 ++++ .../coordination/GlobalAllowList.sol | 4 +- .../contracts/coordination/IFeeModel.sol | 6 +++ tests/test_bqeth_subscription.py | 45 +++++++++++++++++++ 5 files changed, 77 insertions(+), 3 deletions(-) diff --git a/contracts/contracts/coordination/BqETHSubscription.sol b/contracts/contracts/coordination/BqETHSubscription.sol index 7996c0086..5f977d501 100644 --- a/contracts/contracts/coordination/BqETHSubscription.sol +++ b/contracts/contracts/coordination/BqETHSubscription.sol @@ -7,13 +7,13 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "./Coordinator.sol"; import "./IFeeModel.sol"; -using SafeERC20 for IERC20; - /** * @title BqETH Subscription * @notice Manages the subscription information for rituals. */ contract BqETHSubscription is IFeeModel { + using SafeERC20 for IERC20; + Coordinator public immutable coordinator; IERC20 public immutable feeToken; @@ -207,4 +207,17 @@ contract BqETHSubscription is IFeeModel { ) external view override onlyAccessController onlyActiveRitual(ritualId) { require(block.timestamp <= endOfSubscription, "Subscription has expired"); } + + /** + * @dev This function is called before the isAuthorized function + * @param ritualId The ID of the ritual + */ + function beforeIsAuthorized( + uint32 ritualId + ) external view override onlyAccessController onlyActiveRitual(ritualId) { + require( + block.timestamp <= endOfSubscription + yellowPeriodDuration, + "Yellow period has expired" + ); + } } diff --git a/contracts/contracts/coordination/FlatRateFeeModel.sol b/contracts/contracts/coordination/FlatRateFeeModel.sol index d97bd5417..2d4aa4b75 100644 --- a/contracts/contracts/coordination/FlatRateFeeModel.sol +++ b/contracts/contracts/coordination/FlatRateFeeModel.sol @@ -130,4 +130,12 @@ contract FlatRateFeeModel is IFeeModel, Ownable { ) external view { // solhint-disable-previous-line no-empty-blocks } + + /** + * @dev This function is called before the isAuthorized function + * @param ritualId The ID of the ritual + */ + function beforeIsAuthorized(uint32 ritualId) external view { + // solhint-disable-previous-line no-empty-blocks + } } diff --git a/contracts/contracts/coordination/GlobalAllowList.sol b/contracts/contracts/coordination/GlobalAllowList.sol index d3be4198e..249b7edc4 100644 --- a/contracts/contracts/coordination/GlobalAllowList.sol +++ b/contracts/contracts/coordination/GlobalAllowList.sol @@ -83,10 +83,12 @@ contract GlobalAllowList is IEncryptionAuthorizer { */ function _beforeIsAuthorized( uint32 ritualId, + // solhint-disable-next-line no-unused-vars bytes memory evidence, + // solhint-disable-next-line no-unused-vars bytes memory ciphertextHeader ) internal view virtual { - // solhint-disable-previous-line no-empty-blocks + feeModel.beforeIsAuthorized(ritualId); } /** diff --git a/contracts/contracts/coordination/IFeeModel.sol b/contracts/contracts/coordination/IFeeModel.sol index af2d42e74..efb9701dd 100644 --- a/contracts/contracts/coordination/IFeeModel.sol +++ b/contracts/contracts/coordination/IFeeModel.sol @@ -34,4 +34,10 @@ interface IFeeModel { address[] calldata addresses, bool value ) external; + + /** + * @dev This function is called before the isAuthorized function + * @param ritualId The ID of the ritual + */ + function beforeIsAuthorized(uint32 ritualId) external view; } diff --git a/tests/test_bqeth_subscription.py b/tests/test_bqeth_subscription.py index b7e258aba..cb8493d38 100644 --- a/tests/test_bqeth_subscription.py +++ b/tests/test_bqeth_subscription.py @@ -14,10 +14,13 @@ You should have received a copy of the GNU Affero General Public License along with nucypher. If not, see . """ +import os from enum import IntEnum import ape import pytest +from eth_account.messages import encode_defunct +from web3 import Web3 FEE_RATE = 42 MAX_NODES = 10 @@ -363,3 +366,45 @@ def test_before_set_authorization( subscription.paySubscriptionFor(sender=adopter) global_allow_list.authorize(ritual_id, [creator.address], sender=adopter) + + +def test_before_is_authorized( + erc20, subscription, coordinator, adopter, global_allow_list, treasury, creator, chain +): + ritual_id = 6 + + w3 = Web3() + data = os.urandom(32) + digest = Web3.keccak(data) + signable_message = encode_defunct(digest) + signed_digest = w3.eth.account.sign_message(signable_message, private_key=adopter.private_key) + signature = signed_digest.signature + + assert subscription.address == global_allow_list.feeModel() + + with ape.reverts("Only Access Controller can call this method"): + subscription.beforeIsAuthorized(0, sender=adopter) + + with ape.reverts("Ritual must be active"): + global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) + + erc20.approve(subscription.address, 10 * FEE, sender=adopter) + subscription.paySubscriptionFor(sender=adopter) + coordinator.setRitual( + ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury + ) + coordinator.processRitualPayment(adopter, ritual_id, MAX_NODES, DURATION, sender=treasury) + global_allow_list.authorize(ritual_id, [adopter.address], sender=adopter) + + with ape.reverts("Ritual must be active"): + global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) + assert global_allow_list.isAuthorized(ritual_id, bytes(signature), bytes(data)) + + end_subscription = subscription.endOfSubscription() + chain.pending_timestamp = end_subscription + YELLOW_PERIOD + 2 + + with ape.reverts("Yellow period has expired"): + global_allow_list.isAuthorized(ritual_id, bytes(signature), bytes(data)) + + subscription.paySubscriptionFor(sender=adopter) + assert global_allow_list.isAuthorized(ritual_id, bytes(signature), bytes(data)) From cd77f41f3c088bb8324853a5497a7cfac11921f6 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Fri, 7 Jun 2024 15:00:12 -0400 Subject: [PATCH 037/105] Apply suggestions from code review Co-authored-by: Derek Pierre --- .../coordination/BqETHSubscription.sol | 15 ++++---- tests/test_bqeth_subscription.py | 38 +++++++++++-------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/contracts/contracts/coordination/BqETHSubscription.sol b/contracts/contracts/coordination/BqETHSubscription.sol index 5f977d501..c0703ed9b 100644 --- a/contracts/contracts/coordination/BqETHSubscription.sol +++ b/contracts/contracts/coordination/BqETHSubscription.sol @@ -46,8 +46,9 @@ contract BqETHSubscription is IFeeModel { * @notice Emitted when a subscription is paid * @param subscriber The address of the subscriber * @param amount The amount paid + * @param endOfSubscription End timestamp of subscription */ - event SubscriptionPaid(address indexed subscriber, uint256 amount); + event SubscriptionPaid(address indexed subscriber, uint256 amount, uint32 endOfSubscription); /** * @notice Sets the coordinator and fee token contracts @@ -117,7 +118,7 @@ contract BqETHSubscription is IFeeModel { function initialize(IEncryptionAuthorizer _accessController) external { require( address(accessController) == address(0) && address(_accessController) != address(0), - "Access controller cannot be the zero address" + "Access controller not already set and parameter cannot be the zero address" ); accessController = _accessController; activeRitualId = INACTIVE_RITUAL_ID; @@ -130,7 +131,7 @@ contract BqETHSubscription is IFeeModel { /** * @notice Pays for a subscription */ - function paySubscriptionFor() external { + function payForSubscription() external { // require(endOfSubscription == 0, "Subscription already payed"); uint256 amount = packageFees(); if (endOfSubscription == 0) { @@ -139,7 +140,7 @@ contract BqETHSubscription is IFeeModel { endOfSubscription += uint32(maxDuration); feeToken.safeTransferFrom(msg.sender, address(this), amount); - emit SubscriptionPaid(msg.sender, amount); + emit SubscriptionPaid(msg.sender, amount, endOfSubscription); } /** @@ -147,7 +148,7 @@ contract BqETHSubscription is IFeeModel { * @param amount The amount to withdraw */ function withdrawToBeneficiary(uint256 amount) external onlyBeneficiary { - require(amount <= feeToken.balanceOf(address(this)), "Insufficient available amount"); + require(amount <= feeToken.balanceOf(address(this)), "Insufficient balance available"); feeToken.safeTransfer(beneficiary, amount); emit WithdrawalToBeneficiary(beneficiary, amount); } @@ -159,7 +160,7 @@ contract BqETHSubscription is IFeeModel { uint32 duration ) external override onlyCoordinator { require(initiator == adopter, "Only adopter can initiate ritual"); - require(endOfSubscription != 0, "Subscription has to be payed first"); + require(endOfSubscription != 0, "Subscription has to be paid first"); require( endOfSubscription + yellowPeriodDuration + redPeriodDuration >= block.timestamp + duration && @@ -178,7 +179,7 @@ contract BqETHSubscription is IFeeModel { state == Coordinator.RitualState.DKG_INVALID || state == Coordinator.RitualState.DKG_TIMEOUT || state == Coordinator.RitualState.EXPIRED, // TODO check if it's ok - "Only failed rituals allowed to be reinitiate" + "Only failed rituals allowed to be reinitiated" ); } activeRitualId = ritualId; diff --git a/tests/test_bqeth_subscription.py b/tests/test_bqeth_subscription.py index cb8493d38..0f35bbec4 100644 --- a/tests/test_bqeth_subscription.py +++ b/tests/test_bqeth_subscription.py @@ -109,7 +109,7 @@ def test_pay_subscription(erc20, subscription, adopter, chain): balance_before = erc20.balanceOf(adopter) assert subscription.packageFees() == FEE - tx = subscription.paySubscriptionFor(sender=adopter) + tx = subscription.payForSubscription(sender=adopter) timestamp = chain.pending_timestamp - 1 assert subscription.endOfSubscription() == timestamp + MAX_DURATION balance_after = erc20.balanceOf(adopter) @@ -117,18 +117,26 @@ def test_pay_subscription(erc20, subscription, adopter, chain): assert erc20.balanceOf(subscription.address) == FEE events = subscription.SubscriptionPaid.from_receipt(tx) - assert events == [subscription.SubscriptionPaid(subscriber=adopter, amount=FEE)] + assert events == [ + subscription.SubscriptionPaid( + subscriber=adopter, amount=FEE, endOfSubscription=timestamp + MAX_DURATION + ) + ] # Top up balance_before = erc20.balanceOf(adopter) - tx = subscription.paySubscriptionFor(sender=adopter) + tx = subscription.payForSubscription(sender=adopter) assert subscription.endOfSubscription() == timestamp + 2 * MAX_DURATION balance_after = erc20.balanceOf(adopter) assert balance_after + FEE == balance_before assert erc20.balanceOf(subscription.address) == 2 * FEE events = subscription.SubscriptionPaid.from_receipt(tx) - assert events == [subscription.SubscriptionPaid(subscriber=adopter, amount=FEE)] + assert events == [ + subscription.SubscriptionPaid( + subscriber=adopter, amount=FEE, endOfSubscription=timestamp + 2 * MAX_DURATION + ) + ] def test_withdraw(erc20, subscription, adopter, treasury): @@ -137,12 +145,12 @@ def test_withdraw(erc20, subscription, adopter, treasury): with ape.reverts("Only the beneficiary can call this method"): subscription.withdrawToBeneficiary(1, sender=adopter) - with ape.reverts("Insufficient available amount"): + with ape.reverts("Insufficient balance available"): subscription.withdrawToBeneficiary(1, sender=treasury) - subscription.paySubscriptionFor(sender=adopter) + subscription.payForSubscription(sender=adopter) - with ape.reverts("Insufficient available amount"): + with ape.reverts("Insufficient balance available"): subscription.withdrawToBeneficiary(FEE + 1, sender=treasury) tx = subscription.withdrawToBeneficiary(FEE, sender=treasury) @@ -167,13 +175,13 @@ def test_process_ritual_payment( coordinator.processRitualPayment( treasury, ritual_id, number_of_providers, DURATION, sender=treasury ) - with ape.reverts("Subscription has to be payed first"): + with ape.reverts("Subscription has to be paid first"): coordinator.processRitualPayment( adopter, ritual_id, number_of_providers, DURATION, sender=treasury ) erc20.approve(subscription.address, 10 * FEE, sender=adopter) - subscription.paySubscriptionFor(sender=adopter) + subscription.payForSubscription(sender=adopter) with ape.reverts("Ritual parameters exceed available in package"): coordinator.processRitualPayment( @@ -216,7 +224,7 @@ def test_process_ritual_payment( coordinator.setRitual( new_ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury ) - with ape.reverts("Only failed rituals allowed to be reinitiate"): + with ape.reverts("Only failed rituals allowed to be reinitiated"): coordinator.processRitualPayment( adopter, new_ritual_id, number_of_providers, DURATION, sender=treasury ) @@ -272,7 +280,7 @@ def test_process_ritual_extending( ) erc20.approve(subscription.address, 10 * FEE, sender=adopter) - subscription.paySubscriptionFor(sender=adopter) + subscription.payForSubscription(sender=adopter) coordinator.setRitual( ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury ) @@ -346,7 +354,7 @@ def test_before_set_authorization( global_allow_list.authorize(0, [creator.address], sender=adopter) erc20.approve(subscription.address, 10 * FEE, sender=adopter) - subscription.paySubscriptionFor(sender=adopter) + subscription.payForSubscription(sender=adopter) coordinator.setRitual( ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury ) @@ -364,7 +372,7 @@ def test_before_set_authorization( with ape.reverts("Subscription has expired"): global_allow_list.authorize(ritual_id, [creator.address], sender=adopter) - subscription.paySubscriptionFor(sender=adopter) + subscription.payForSubscription(sender=adopter) global_allow_list.authorize(ritual_id, [creator.address], sender=adopter) @@ -389,7 +397,7 @@ def test_before_is_authorized( global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) erc20.approve(subscription.address, 10 * FEE, sender=adopter) - subscription.paySubscriptionFor(sender=adopter) + subscription.payForSubscription(sender=adopter) coordinator.setRitual( ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury ) @@ -406,5 +414,5 @@ def test_before_is_authorized( with ape.reverts("Yellow period has expired"): global_allow_list.isAuthorized(ritual_id, bytes(signature), bytes(data)) - subscription.paySubscriptionFor(sender=adopter) + subscription.payForSubscription(sender=adopter) assert global_allow_list.isAuthorized(ritual_id, bytes(signature), bytes(data)) From 3f201d6a58a0d6f09f2c502da512aa5021576d91 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Mon, 10 Jun 2024 18:35:29 -0400 Subject: [PATCH 038/105] Extract base logic from BqETHSubscription --- .../coordination/AbstractSubscription.sol | 87 +++++++++++++++++++ .../coordination/BqETHSubscription.sol | 80 ++++------------- tests/test_bqeth_subscription.py | 10 +-- 3 files changed, 108 insertions(+), 69 deletions(-) create mode 100644 contracts/contracts/coordination/AbstractSubscription.sol diff --git a/contracts/contracts/coordination/AbstractSubscription.sol b/contracts/contracts/coordination/AbstractSubscription.sol new file mode 100644 index 000000000..723dea3ee --- /dev/null +++ b/contracts/contracts/coordination/AbstractSubscription.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.0; + +import "./Coordinator.sol"; +import "./IFeeModel.sol"; + +/** + * @title Base Subscription contract + * @notice Manages the subscription information for rituals. + */ +abstract contract AbstractSubscription is IFeeModel { + Coordinator public immutable coordinator; + + uint32 public immutable maxDuration; + uint32 public immutable yellowPeriodDuration; + uint32 public immutable redPeriodDuration; + + /** + * @notice Sets subscription parameters + * @dev The coordinator and fee token contracts cannot be zero addresses + * @param _coordinator The address of the coordinator contract + * @param _maxDuration Maximum duration of ritual + * @param _yellowPeriodDuration Duration of yellow period + * @param _redPeriodDuration Duration of red period + */ + constructor( + Coordinator _coordinator, + uint32 _maxDuration, + uint32 _yellowPeriodDuration, + uint32 _redPeriodDuration + ) { + require(address(_coordinator) != address(0), "Coordinator cannot be the zero address"); + coordinator = _coordinator; + maxDuration = _maxDuration; + yellowPeriodDuration = _yellowPeriodDuration; + redPeriodDuration = _redPeriodDuration; + } + + modifier onlyCoordinator() { + require(msg.sender == address(coordinator), "Only the Coordinator can call this method"); + _; + } + + modifier onlyAccessController() virtual; + + modifier onlyActiveRitual(uint32 ritualId) virtual; + + function getEndOfSubscription() public view virtual returns (uint32); + + function processRitualExtending( + address, + uint32 ritualId, + uint256, + uint32 + ) external view override onlyCoordinator onlyActiveRitual(ritualId) { + (, uint32 endTimestamp) = coordinator.getTimestamps(ritualId); + require( + getEndOfSubscription() + yellowPeriodDuration + redPeriodDuration >= endTimestamp, + "Ritual parameters exceed available in package" + ); + } + + /** + * @dev This function is called before the setAuthorizations function + */ + function beforeSetAuthorization( + uint32 ritualId, + address[] calldata, + bool + ) external view override onlyAccessController onlyActiveRitual(ritualId) { + require(block.timestamp <= getEndOfSubscription(), "Subscription has expired"); + } + + /** + * @dev This function is called before the isAuthorized function + * @param ritualId The ID of the ritual + */ + function beforeIsAuthorized( + uint32 ritualId + ) external view override onlyAccessController onlyActiveRitual(ritualId) { + require( + block.timestamp <= getEndOfSubscription() + yellowPeriodDuration, + "Yellow period has expired" + ); + } +} diff --git a/contracts/contracts/coordination/BqETHSubscription.sol b/contracts/contracts/coordination/BqETHSubscription.sol index c0703ed9b..3092cc245 100644 --- a/contracts/contracts/coordination/BqETHSubscription.sol +++ b/contracts/contracts/coordination/BqETHSubscription.sol @@ -4,17 +4,15 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "./Coordinator.sol"; -import "./IFeeModel.sol"; +import "./AbstractSubscription.sol"; /** * @title BqETH Subscription * @notice Manages the subscription information for rituals. */ -contract BqETHSubscription is IFeeModel { +contract BqETHSubscription is AbstractSubscription { using SafeERC20 for IERC20; - Coordinator public immutable coordinator; IERC20 public immutable feeToken; uint32 public constant INACTIVE_RITUAL_ID = type(uint32).max; @@ -26,13 +24,10 @@ contract BqETHSubscription is IFeeModel { uint256 public immutable feeRate; uint256 public immutable maxNodes; - uint32 public immutable maxDuration; - uint32 public immutable yellowPeriodDuration; - uint32 public immutable redPeriodDuration; IEncryptionAuthorizer public accessController; - uint32 public endOfSubscription; + uint32 internal _endOfSubscription; uint32 public activeRitualId; /** @@ -73,25 +68,15 @@ contract BqETHSubscription is IFeeModel { uint32 _maxDuration, uint32 _yellowPeriodDuration, uint32 _redPeriodDuration - ) { - require(address(_coordinator) != address(0), "Coordinator cannot be the zero address"); + ) AbstractSubscription(_coordinator, _maxDuration, _yellowPeriodDuration, _redPeriodDuration) { require(address(_feeToken) != address(0), "Fee token cannot be the zero address"); require(_beneficiary != address(0), "Beneficiary cannot be the zero address"); require(_adopter != address(0), "Adopter cannot be the zero address"); - coordinator = _coordinator; feeToken = _feeToken; beneficiary = _beneficiary; adopter = _adopter; feeRate = _feeRate; maxNodes = _maxNodes; - maxDuration = _maxDuration; - yellowPeriodDuration = _yellowPeriodDuration; - redPeriodDuration = _redPeriodDuration; - } - - modifier onlyCoordinator() { - require(msg.sender == address(coordinator), "Only the Coordinator can call this method"); - _; } modifier onlyBeneficiary() { @@ -99,7 +84,7 @@ contract BqETHSubscription is IFeeModel { _; } - modifier onlyAccessController() { + modifier onlyAccessController() override { require( msg.sender == address(accessController), "Only Access Controller can call this method" @@ -107,7 +92,7 @@ contract BqETHSubscription is IFeeModel { _; } - modifier onlyActiveRitual(uint32 ritualId) { + modifier onlyActiveRitual(uint32 ritualId) override { require( activeRitualId != INACTIVE_RITUAL_ID && ritualId == activeRitualId, "Ritual must be active" @@ -115,6 +100,10 @@ contract BqETHSubscription is IFeeModel { _; } + function getEndOfSubscription() public view override returns (uint32) { + return _endOfSubscription; + } + function initialize(IEncryptionAuthorizer _accessController) external { require( address(accessController) == address(0) && address(_accessController) != address(0), @@ -134,13 +123,13 @@ contract BqETHSubscription is IFeeModel { function payForSubscription() external { // require(endOfSubscription == 0, "Subscription already payed"); uint256 amount = packageFees(); - if (endOfSubscription == 0) { - endOfSubscription = uint32(block.timestamp); + if (_endOfSubscription == 0) { + _endOfSubscription = uint32(block.timestamp); } - endOfSubscription += uint32(maxDuration); + _endOfSubscription += uint32(maxDuration); feeToken.safeTransferFrom(msg.sender, address(this), amount); - emit SubscriptionPaid(msg.sender, amount, endOfSubscription); + emit SubscriptionPaid(msg.sender, amount, _endOfSubscription); } /** @@ -160,9 +149,9 @@ contract BqETHSubscription is IFeeModel { uint32 duration ) external override onlyCoordinator { require(initiator == adopter, "Only adopter can initiate ritual"); - require(endOfSubscription != 0, "Subscription has to be paid first"); + require(_endOfSubscription != 0, "Subscription has to be paid first"); require( - endOfSubscription + yellowPeriodDuration + redPeriodDuration >= + _endOfSubscription + yellowPeriodDuration + redPeriodDuration >= block.timestamp + duration && numberOfProviders <= maxNodes, "Ritual parameters exceed available in package" @@ -184,41 +173,4 @@ contract BqETHSubscription is IFeeModel { } activeRitualId = ritualId; } - - function processRitualExtending( - address, - uint32 ritualId, - uint256, - uint32 - ) external view override onlyCoordinator onlyActiveRitual(ritualId) { - (, uint32 endTimestamp) = coordinator.getTimestamps(ritualId); - require( - endOfSubscription + yellowPeriodDuration + redPeriodDuration >= endTimestamp, - "Ritual parameters exceed available in package" - ); - } - - /** - * @dev This function is called before the setAuthorizations function - */ - function beforeSetAuthorization( - uint32 ritualId, - address[] calldata, - bool - ) external view override onlyAccessController onlyActiveRitual(ritualId) { - require(block.timestamp <= endOfSubscription, "Subscription has expired"); - } - - /** - * @dev This function is called before the isAuthorized function - * @param ritualId The ID of the ritual - */ - function beforeIsAuthorized( - uint32 ritualId - ) external view override onlyAccessController onlyActiveRitual(ritualId) { - require( - block.timestamp <= endOfSubscription + yellowPeriodDuration, - "Yellow period has expired" - ); - } } diff --git a/tests/test_bqeth_subscription.py b/tests/test_bqeth_subscription.py index 0f35bbec4..47180a20c 100644 --- a/tests/test_bqeth_subscription.py +++ b/tests/test_bqeth_subscription.py @@ -111,7 +111,7 @@ def test_pay_subscription(erc20, subscription, adopter, chain): tx = subscription.payForSubscription(sender=adopter) timestamp = chain.pending_timestamp - 1 - assert subscription.endOfSubscription() == timestamp + MAX_DURATION + assert subscription.getEndOfSubscription() == timestamp + MAX_DURATION balance_after = erc20.balanceOf(adopter) assert balance_after + FEE == balance_before assert erc20.balanceOf(subscription.address) == FEE @@ -126,7 +126,7 @@ def test_pay_subscription(erc20, subscription, adopter, chain): # Top up balance_before = erc20.balanceOf(adopter) tx = subscription.payForSubscription(sender=adopter) - assert subscription.endOfSubscription() == timestamp + 2 * MAX_DURATION + assert subscription.getEndOfSubscription() == timestamp + 2 * MAX_DURATION balance_after = erc20.balanceOf(adopter) assert balance_after + FEE == balance_before assert erc20.balanceOf(subscription.address) == 2 * FEE @@ -287,7 +287,7 @@ def test_process_ritual_extending( coordinator.processRitualPayment( adopter, ritual_id, number_of_providers, DURATION, sender=treasury ) - end_subscription = subscription.endOfSubscription() + end_subscription = subscription.getEndOfSubscription() max_end_timestamp = end_subscription + YELLOW_PERIOD + RED_PERIOD new_ritual_id = ritual_id + 1 @@ -366,7 +366,7 @@ def test_before_set_authorization( global_allow_list.authorize(0, [creator.address], sender=adopter) global_allow_list.authorize(ritual_id, [creator.address], sender=adopter) - end_subscription = subscription.endOfSubscription() + end_subscription = subscription.getEndOfSubscription() chain.pending_timestamp = end_subscription + 1 with ape.reverts("Subscription has expired"): @@ -408,7 +408,7 @@ def test_before_is_authorized( global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) assert global_allow_list.isAuthorized(ritual_id, bytes(signature), bytes(data)) - end_subscription = subscription.endOfSubscription() + end_subscription = subscription.getEndOfSubscription() chain.pending_timestamp = end_subscription + YELLOW_PERIOD + 2 with ape.reverts("Yellow period has expired"): From 9db689cec9ad69ebec01e988c756c866d56285d0 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Wed, 12 Jun 2024 12:36:14 -0400 Subject: [PATCH 039/105] BqETHSubscription: draft of encryptor slots and fees --- .../coordination/AbstractSubscription.sol | 12 +- .../coordination/BqETHSubscription.sol | 165 +++++++++++++++--- .../contracts/coordination/Coordinator.sol | 1 + 3 files changed, 149 insertions(+), 29 deletions(-) diff --git a/contracts/contracts/coordination/AbstractSubscription.sol b/contracts/contracts/coordination/AbstractSubscription.sol index 723dea3ee..5ef7deced 100644 --- a/contracts/contracts/coordination/AbstractSubscription.sol +++ b/contracts/contracts/coordination/AbstractSubscription.sol @@ -12,7 +12,7 @@ import "./IFeeModel.sol"; abstract contract AbstractSubscription is IFeeModel { Coordinator public immutable coordinator; - uint32 public immutable maxDuration; + uint32 public immutable packageDuration; uint32 public immutable yellowPeriodDuration; uint32 public immutable redPeriodDuration; @@ -20,19 +20,19 @@ abstract contract AbstractSubscription is IFeeModel { * @notice Sets subscription parameters * @dev The coordinator and fee token contracts cannot be zero addresses * @param _coordinator The address of the coordinator contract - * @param _maxDuration Maximum duration of ritual + * @param _packageDuration Maximum duration of ritual * @param _yellowPeriodDuration Duration of yellow period * @param _redPeriodDuration Duration of red period */ constructor( Coordinator _coordinator, - uint32 _maxDuration, + uint32 _packageDuration, uint32 _yellowPeriodDuration, uint32 _redPeriodDuration ) { require(address(_coordinator) != address(0), "Coordinator cannot be the zero address"); coordinator = _coordinator; - maxDuration = _maxDuration; + packageDuration = _packageDuration; yellowPeriodDuration = _yellowPeriodDuration; redPeriodDuration = _redPeriodDuration; } @@ -68,7 +68,7 @@ abstract contract AbstractSubscription is IFeeModel { uint32 ritualId, address[] calldata, bool - ) external view override onlyAccessController onlyActiveRitual(ritualId) { + ) public virtual override onlyAccessController onlyActiveRitual(ritualId) { require(block.timestamp <= getEndOfSubscription(), "Subscription has expired"); } @@ -78,7 +78,7 @@ abstract contract AbstractSubscription is IFeeModel { */ function beforeIsAuthorized( uint32 ritualId - ) external view override onlyAccessController onlyActiveRitual(ritualId) { + ) public view virtual override onlyAccessController onlyActiveRitual(ritualId) { require( block.timestamp <= getEndOfSubscription() + yellowPeriodDuration, "Yellow period has expired" diff --git a/contracts/contracts/coordination/BqETHSubscription.sol b/contracts/contracts/coordination/BqETHSubscription.sol index 3092cc245..85963f053 100644 --- a/contracts/contracts/coordination/BqETHSubscription.sol +++ b/contracts/contracts/coordination/BqETHSubscription.sol @@ -13,6 +13,11 @@ import "./AbstractSubscription.sol"; contract BqETHSubscription is AbstractSubscription { using SafeERC20 for IERC20; + struct Billing { + bool paid; + uint128 encryptorSlots; // pre-paid encryptor slots for the billing period + } + IERC20 public immutable feeToken; uint32 public constant INACTIVE_RITUAL_ID = type(uint32).max; @@ -22,14 +27,19 @@ contract BqETHSubscription is AbstractSubscription { address public immutable beneficiary; address public immutable adopter; - uint256 public immutable feeRate; + uint256 public immutable baseFeeRate; + uint256 public immutable encryptorFeeRate; + uint256 public immutable enryptorsFeeRate; uint256 public immutable maxNodes; IEncryptionAuthorizer public accessController; - uint32 internal _endOfSubscription; + uint32 public startOfSubscription; uint32 public activeRitualId; + uint256 public usedEncryptorSlots; + mapping(uint256 periodNumber => Billing billing) public billingInfo; + /** * @notice Emitted when a subscription is spent * @param beneficiary The address of the beneficiary @@ -41,9 +51,29 @@ contract BqETHSubscription is AbstractSubscription { * @notice Emitted when a subscription is paid * @param subscriber The address of the subscriber * @param amount The amount paid + * @param encryptorSlots Number of encryptor slots * @param endOfSubscription End timestamp of subscription */ - event SubscriptionPaid(address indexed subscriber, uint256 amount, uint32 endOfSubscription); + event SubscriptionPaid( + address indexed subscriber, + uint256 amount, + uint128 encryptorSlots, + uint32 endOfSubscription + ); + + /** + * @notice Emitted when a subscription is paid + * @param subscriber The address of the subscriber + * @param amount The amount paid + * @param encryptorSlots Number of encryptor slots + * @param endOfCurrentPeriod End timestamp of the current billing period + */ + event EncryptorSlotsPaid( + address indexed subscriber, + uint256 amount, + uint128 encryptorSlots, + uint32 endOfCurrentPeriod + ); /** * @notice Sets the coordinator and fee token contracts @@ -52,9 +82,10 @@ contract BqETHSubscription is AbstractSubscription { * @param _feeToken The address of the fee token contract * @param _beneficiary The address of the beneficiary * @param _adopter The address of the adopter - * @param _feeRate Fee rate per node per second + * @param _baseFeeRate Fee rate per node per second + * @param _encryptorFeeRate Fee rate per encryptor per second * @param _maxNodes Maximum nodes in the package - * @param _maxDuration Maximum duration of ritual + * @param _packageDuration Maximum duration of ritual * @param _yellowPeriodDuration Duration of yellow period * @param _redPeriodDuration Duration of red period */ @@ -63,19 +94,28 @@ contract BqETHSubscription is AbstractSubscription { IERC20 _feeToken, address _beneficiary, address _adopter, - uint256 _feeRate, + uint256 _baseFeeRate, + uint256 _encryptorFeeRate, uint256 _maxNodes, - uint32 _maxDuration, + uint32 _packageDuration, uint32 _yellowPeriodDuration, uint32 _redPeriodDuration - ) AbstractSubscription(_coordinator, _maxDuration, _yellowPeriodDuration, _redPeriodDuration) { + ) + AbstractSubscription( + _coordinator, + _packageDuration, + _yellowPeriodDuration, + _redPeriodDuration + ) + { require(address(_feeToken) != address(0), "Fee token cannot be the zero address"); require(_beneficiary != address(0), "Beneficiary cannot be the zero address"); require(_adopter != address(0), "Adopter cannot be the zero address"); feeToken = _feeToken; beneficiary = _beneficiary; adopter = _adopter; - feeRate = _feeRate; + baseFeeRate = _baseFeeRate; + encryptorFeeRate = _encryptorFeeRate; maxNodes = _maxNodes; } @@ -100,8 +140,33 @@ contract BqETHSubscription is AbstractSubscription { _; } - function getEndOfSubscription() public view override returns (uint32) { - return _endOfSubscription; + function getCurrentPeriodNumber() public view returns (uint256) { + if (startOfSubscription == 0) { + return 0; + } + return (block.timestamp - startOfSubscription) / packageDuration; + } + + function getEndOfSubscription() public view override returns (uint32 endOfSubscription) { + if (startOfSubscription == 0) { + return 0; + } + + uint256 currentPeriodNumber = getCurrentPeriodNumber(); + Billing storage billing = billingInfo[currentPeriodNumber]; + if (billing.paid) { + while (billing.paid) { + currentPeriodNumber++; + billing = billingInfo[currentPeriodNumber]; + } + } else { + while (!billing.paid) { + currentPeriodNumber--; + billing = billingInfo[currentPeriodNumber]; + } + currentPeriodNumber++; + } + endOfSubscription = uint32(startOfSubscription + currentPeriodNumber * packageDuration); } function initialize(IEncryptionAuthorizer _accessController) external { @@ -113,23 +178,59 @@ contract BqETHSubscription is AbstractSubscription { activeRitualId = INACTIVE_RITUAL_ID; } - function packageFees() public view returns (uint256) { - return feeRate * maxDuration * maxNodes; + function baseFees() public view returns (uint256) { + return baseFeeRate * packageDuration * maxNodes; + } + + function encryptorFees(uint128 encryptorSlots, uint32 duration) public view returns (uint256) { + return encryptorFeeRate * duration * encryptorSlots; } /** * @notice Pays for a subscription + * @param encryptorSlots Number of slots for encryptors */ - function payForSubscription() external { - // require(endOfSubscription == 0, "Subscription already payed"); - uint256 amount = packageFees(); - if (_endOfSubscription == 0) { - _endOfSubscription = uint32(block.timestamp); + function payForSubscription(uint128 encryptorSlots) external { + uint256 currentPeriodNumber = getCurrentPeriodNumber(); + require(!billingInfo[currentPeriodNumber + 1].paid, "Next billing period already paid"); // TODO until we will have refunds + require( + getEndOfSubscription() + yellowPeriodDuration + redPeriodDuration >= block.timestamp, + "Subscription is over" + ); + + uint256 fees = baseFees() + encryptorFees(encryptorSlots, packageDuration); + uint256 periodNumber = currentPeriodNumber; + if (startOfSubscription == 0) { + startOfSubscription = uint32(block.timestamp); } - _endOfSubscription += uint32(maxDuration); + if (billingInfo[periodNumber].paid) { + periodNumber++; + } + Billing storage billing = billingInfo[periodNumber]; + billing.paid = true; + billing.encryptorSlots = encryptorSlots; + + feeToken.safeTransferFrom(msg.sender, address(this), fees); + emit SubscriptionPaid(msg.sender, fees, encryptorSlots, getEndOfSubscription()); + } + + /** + * @notice Pays for additional encryptor slots + * @param encryptorSlots Number of slots for encryptors + */ + function payForEncryptorSlots(uint128 encryptorSlots) external { + uint256 currentPeriodNumber = getCurrentPeriodNumber(); + Billing storage billing = billingInfo[currentPeriodNumber]; + require(billing.paid, "Current billing period must be paid"); + + uint32 endOfCurrentPeriod = uint32( + startOfSubscription + (currentPeriodNumber + 1) * packageDuration + ); + uint256 fees = encryptorFees(encryptorSlots, endOfCurrentPeriod - uint32(block.timestamp)); + billing.encryptorSlots += encryptorSlots; - feeToken.safeTransferFrom(msg.sender, address(this), amount); - emit SubscriptionPaid(msg.sender, amount, _endOfSubscription); + feeToken.safeTransferFrom(msg.sender, address(this), fees); + emit EncryptorSlotsPaid(msg.sender, fees, encryptorSlots, endOfCurrentPeriod); } /** @@ -149,9 +250,10 @@ contract BqETHSubscription is AbstractSubscription { uint32 duration ) external override onlyCoordinator { require(initiator == adopter, "Only adopter can initiate ritual"); - require(_endOfSubscription != 0, "Subscription has to be paid first"); + uint32 endOfSubscription = getEndOfSubscription(); + require(endOfSubscription != 0, "Subscription has to be paid first"); require( - _endOfSubscription + yellowPeriodDuration + redPeriodDuration >= + endOfSubscription + yellowPeriodDuration + redPeriodDuration >= block.timestamp + duration && numberOfProviders <= maxNodes, "Ritual parameters exceed available in package" @@ -173,4 +275,21 @@ contract BqETHSubscription is AbstractSubscription { } activeRitualId = ritualId; } + + function beforeSetAuthorization( + uint32 ritualId, + address[] calldata addresses, + bool value + ) public override { + super.beforeSetAuthorization(ritualId, addresses, value); + if (value) { + uint256 currentPeriodNumber = getCurrentPeriodNumber(); + Billing storage billing = billingInfo[currentPeriodNumber]; + uint128 encryptorSlots = billing.paid ? billing.encryptorSlots : 0; + usedEncryptorSlots += addresses.length; + require(usedEncryptorSlots <= encryptorSlots, "Encryptors slots filled up"); + } else { + usedEncryptorSlots -= addresses.length; + } + } } diff --git a/contracts/contracts/coordination/Coordinator.sol b/contracts/contracts/coordination/Coordinator.sol index 83a237dce..ce970e765 100644 --- a/contracts/contracts/coordination/Coordinator.sol +++ b/contracts/contracts/coordination/Coordinator.sol @@ -117,6 +117,7 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable constructor(ITACoChildApplication _application) { application = _application; minAuthorization = _application.minimumAuthorization(); // TODO use child app for checking eligibility + _disableInitializers(); } /** From f8830a23f14907f5229b5337af58c0c536b5952b Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Thu, 13 Jun 2024 18:03:12 -0400 Subject: [PATCH 040/105] BqETHSubscription: tests and fixes --- .../coordination/BqETHSubscription.sol | 4 +- .../coordination/GlobalAllowList.sol | 5 +- tests/test_bqeth_subscription.py | 212 ++++++++++++++---- tests/test_managed_allow_list.py | 1 + 4 files changed, 178 insertions(+), 44 deletions(-) diff --git a/contracts/contracts/coordination/BqETHSubscription.sol b/contracts/contracts/coordination/BqETHSubscription.sol index 85963f053..79cc1cb48 100644 --- a/contracts/contracts/coordination/BqETHSubscription.sol +++ b/contracts/contracts/coordination/BqETHSubscription.sol @@ -194,7 +194,9 @@ contract BqETHSubscription is AbstractSubscription { uint256 currentPeriodNumber = getCurrentPeriodNumber(); require(!billingInfo[currentPeriodNumber + 1].paid, "Next billing period already paid"); // TODO until we will have refunds require( - getEndOfSubscription() + yellowPeriodDuration + redPeriodDuration >= block.timestamp, + startOfSubscription == 0 || + getEndOfSubscription() + yellowPeriodDuration + redPeriodDuration >= + block.timestamp, "Subscription is over" ); diff --git a/contracts/contracts/coordination/GlobalAllowList.sol b/contracts/contracts/coordination/GlobalAllowList.sol index 249b7edc4..c7658ac88 100644 --- a/contracts/contracts/coordination/GlobalAllowList.sol +++ b/contracts/contracts/coordination/GlobalAllowList.sol @@ -161,7 +161,10 @@ contract GlobalAllowList is IEncryptionAuthorizer { _beforeSetAuthorization(ritualId, addresses, value); for (uint256 i = 0; i < addresses.length; i++) { - authorizations[LookupKey.lookupKey(ritualId, addresses[i])] = value; + bytes32 lookupKey = LookupKey.lookupKey(ritualId, addresses[i]); + // prevent reusing same address + require(authorizations[lookupKey] != value, "Authorization already set"); + authorizations[lookupKey] = value; emit AddressAuthorizationSet(ritualId, addresses[i], value); } diff --git a/tests/test_bqeth_subscription.py b/tests/test_bqeth_subscription.py index 47180a20c..6bb12df42 100644 --- a/tests/test_bqeth_subscription.py +++ b/tests/test_bqeth_subscription.py @@ -22,19 +22,20 @@ from eth_account.messages import encode_defunct from web3 import Web3 -FEE_RATE = 42 +BASE_FEE_RATE = 42 MAX_NODES = 10 +ENCRYPTORS_FEE_RATE = 77 ERC20_SUPPLY = 10**24 -DURATION = 48 * 60 * 60 ONE_DAY = 24 * 60 * 60 +DURATION = 10 * ONE_DAY -MAX_DURATION = 3 * DURATION +PACKAGE_DURATION = 3 * DURATION YELLOW_PERIOD = ONE_DAY RED_PERIOD = 5 * ONE_DAY -FEE = FEE_RATE * MAX_DURATION * MAX_NODES +BASE_FEE = BASE_FEE_RATE * PACKAGE_DURATION * MAX_NODES RitualState = IntEnum( "RitualState", @@ -82,9 +83,10 @@ def subscription(project, creator, coordinator, erc20, treasury, adopter): erc20.address, treasury, adopter, - FEE_RATE, + BASE_FEE_RATE, + ENCRYPTORS_FEE_RATE, MAX_NODES, - MAX_DURATION, + PACKAGE_DURATION, YELLOW_PERIOD, RED_PERIOD, sender=creator, @@ -103,44 +105,148 @@ def global_allow_list(project, creator, coordinator, subscription, treasury): def test_pay_subscription(erc20, subscription, adopter, chain): - erc20.approve(subscription.address, 10 * FEE, sender=adopter) + erc20.approve(subscription.address, 10 * BASE_FEE, sender=adopter) # First payment balance_before = erc20.balanceOf(adopter) - assert subscription.packageFees() == FEE + assert subscription.baseFees() == BASE_FEE + assert subscription.startOfSubscription() == 0 + assert subscription.getEndOfSubscription() == 0 + assert subscription.getCurrentPeriodNumber() == 0 + assert subscription.billingInfo(0) == (False, 0) - tx = subscription.payForSubscription(sender=adopter) + tx = subscription.payForSubscription(0, sender=adopter) timestamp = chain.pending_timestamp - 1 - assert subscription.getEndOfSubscription() == timestamp + MAX_DURATION + assert subscription.startOfSubscription() == timestamp + assert subscription.getEndOfSubscription() == timestamp + PACKAGE_DURATION + assert subscription.getCurrentPeriodNumber() == 0 + assert subscription.billingInfo(0) == (True, 0) + assert subscription.billingInfo(1) == (False, 0) balance_after = erc20.balanceOf(adopter) - assert balance_after + FEE == balance_before - assert erc20.balanceOf(subscription.address) == FEE + assert balance_after + BASE_FEE == balance_before + assert erc20.balanceOf(subscription.address) == BASE_FEE events = subscription.SubscriptionPaid.from_receipt(tx) assert events == [ subscription.SubscriptionPaid( - subscriber=adopter, amount=FEE, endOfSubscription=timestamp + MAX_DURATION + subscriber=adopter, + amount=BASE_FEE, + encryptorSlots=0, + endOfSubscription=timestamp + PACKAGE_DURATION, ) ] # Top up + encryptor_slots = 10 + encryptor_fees = ENCRYPTORS_FEE_RATE * PACKAGE_DURATION * encryptor_slots balance_before = erc20.balanceOf(adopter) - tx = subscription.payForSubscription(sender=adopter) - assert subscription.getEndOfSubscription() == timestamp + 2 * MAX_DURATION + tx = subscription.payForSubscription(10, sender=adopter) + end_subscription = timestamp + 2 * PACKAGE_DURATION + assert subscription.getEndOfSubscription() == end_subscription balance_after = erc20.balanceOf(adopter) - assert balance_after + FEE == balance_before - assert erc20.balanceOf(subscription.address) == 2 * FEE + assert balance_after + BASE_FEE + encryptor_fees == balance_before + assert erc20.balanceOf(subscription.address) == 2 * BASE_FEE + encryptor_fees + assert subscription.getCurrentPeriodNumber() == 0 + assert subscription.billingInfo(0) == (True, 0) + assert subscription.billingInfo(1) == (True, encryptor_slots) events = subscription.SubscriptionPaid.from_receipt(tx) assert events == [ subscription.SubscriptionPaid( - subscriber=adopter, amount=FEE, endOfSubscription=timestamp + 2 * MAX_DURATION + subscriber=adopter, + amount=BASE_FEE + encryptor_fees, + encryptorSlots=encryptor_slots, + endOfSubscription=end_subscription, ) ] + # Can't pay in advance more than one time cycle + with ape.reverts("Next billing period already paid"): + subscription.payForSubscription(0, sender=adopter) + + # Can't pay after red period is over + chain.pending_timestamp = end_subscription + YELLOW_PERIOD + RED_PERIOD + 1 + assert subscription.getCurrentPeriodNumber() == 2 + with ape.reverts("Subscription is over"): + subscription.payForSubscription(0, sender=adopter) + + +def test_pay_encryptor_slots(erc20, subscription, adopter, chain): + encryptor_slots = 10 + assert ( + subscription.encryptorFees(encryptor_slots, PACKAGE_DURATION) + == encryptor_slots * PACKAGE_DURATION * ENCRYPTORS_FEE_RATE + ) + + erc20.approve(subscription.address, 10 * BASE_FEE, sender=adopter) + + with ape.reverts("Current billing period must be paid"): + subscription.payForEncryptorSlots(encryptor_slots, sender=adopter) + + subscription.payForSubscription(encryptor_slots, sender=adopter) + timestamp = chain.pending_timestamp - 1 + subscription.payForSubscription(0, sender=adopter) + assert subscription.billingInfo(0) == (True, encryptor_slots) + assert subscription.billingInfo(1) == (True, 0) + + duration = PACKAGE_DURATION // 3 + chain.pending_timestamp = timestamp + duration + encryptor_fees = encryptor_slots * (PACKAGE_DURATION - duration) * ENCRYPTORS_FEE_RATE + assert ( + subscription.encryptorFees(encryptor_slots, PACKAGE_DURATION - duration) == encryptor_fees + ) + + adopter_balance_before = erc20.balanceOf(adopter) + subscription_balance_before = erc20.balanceOf(subscription.address) + tx = subscription.payForEncryptorSlots(encryptor_slots, sender=adopter) + adopter_balance_after = erc20.balanceOf(adopter) + subscription_balance_after = erc20.balanceOf(subscription.address) + assert adopter_balance_after + encryptor_fees == adopter_balance_before + assert subscription_balance_before + encryptor_fees == subscription_balance_after + assert subscription.billingInfo(0) == (True, 2 * encryptor_slots) + assert subscription.billingInfo(1) == (True, 0) + + events = subscription.EncryptorSlotsPaid.from_receipt(tx) + assert events == [ + subscription.EncryptorSlotsPaid( + subscriber=adopter, + amount=encryptor_fees, + encryptorSlots=encryptor_slots, + endOfCurrentPeriod=timestamp + PACKAGE_DURATION, + ) + ] + + duration = PACKAGE_DURATION // 5 + chain.pending_timestamp = timestamp + PACKAGE_DURATION + duration + encryptor_fees = encryptor_slots * (PACKAGE_DURATION - duration) * ENCRYPTORS_FEE_RATE + + adopter_balance_before = erc20.balanceOf(adopter) + subscription_balance_before = erc20.balanceOf(subscription.address) + tx = subscription.payForEncryptorSlots(encryptor_slots, sender=adopter) + adopter_balance_after = erc20.balanceOf(adopter) + subscription_balance_after = erc20.balanceOf(subscription.address) + assert adopter_balance_after + encryptor_fees == adopter_balance_before + assert subscription_balance_before + encryptor_fees == subscription_balance_after + assert subscription.billingInfo(0) == (True, 2 * encryptor_slots) + assert subscription.billingInfo(1) == (True, encryptor_slots) + + events = subscription.EncryptorSlotsPaid.from_receipt(tx) + assert events == [ + subscription.EncryptorSlotsPaid( + subscriber=adopter, + amount=encryptor_fees, + encryptorSlots=encryptor_slots, + endOfCurrentPeriod=timestamp + 2 * PACKAGE_DURATION, + ) + ] + + chain.pending_timestamp = timestamp + 2 * PACKAGE_DURATION + duration + with ape.reverts("Current billing period must be paid"): + subscription.payForEncryptorSlots(encryptor_slots, sender=adopter) + def test_withdraw(erc20, subscription, adopter, treasury): - erc20.approve(subscription.address, 10 * FEE, sender=adopter) + erc20.approve(subscription.address, 10 * BASE_FEE, sender=adopter) with ape.reverts("Only the beneficiary can call this method"): subscription.withdrawToBeneficiary(1, sender=adopter) @@ -148,17 +254,17 @@ def test_withdraw(erc20, subscription, adopter, treasury): with ape.reverts("Insufficient balance available"): subscription.withdrawToBeneficiary(1, sender=treasury) - subscription.payForSubscription(sender=adopter) + subscription.payForSubscription(0, sender=adopter) with ape.reverts("Insufficient balance available"): - subscription.withdrawToBeneficiary(FEE + 1, sender=treasury) + subscription.withdrawToBeneficiary(BASE_FEE + 1, sender=treasury) - tx = subscription.withdrawToBeneficiary(FEE, sender=treasury) - assert erc20.balanceOf(treasury) == FEE + tx = subscription.withdrawToBeneficiary(BASE_FEE, sender=treasury) + assert erc20.balanceOf(treasury) == BASE_FEE assert erc20.balanceOf(subscription.address) == 0 events = subscription.WithdrawalToBeneficiary.from_receipt(tx) - assert events == [subscription.WithdrawalToBeneficiary(beneficiary=treasury, amount=FEE)] + assert events == [subscription.WithdrawalToBeneficiary(beneficiary=treasury, amount=BASE_FEE)] def test_process_ritual_payment( @@ -180,8 +286,8 @@ def test_process_ritual_payment( adopter, ritual_id, number_of_providers, DURATION, sender=treasury ) - erc20.approve(subscription.address, 10 * FEE, sender=adopter) - subscription.payForSubscription(sender=adopter) + erc20.approve(subscription.address, 10 * BASE_FEE, sender=adopter) + subscription.payForSubscription(0, sender=adopter) with ape.reverts("Ritual parameters exceed available in package"): coordinator.processRitualPayment( @@ -192,7 +298,7 @@ def test_process_ritual_payment( adopter, ritual_id, number_of_providers, - MAX_DURATION + YELLOW_PERIOD + RED_PERIOD + 1, + PACKAGE_DURATION + YELLOW_PERIOD + RED_PERIOD + 1, sender=treasury, ) @@ -203,7 +309,7 @@ def test_process_ritual_payment( adopter, ritual_id, MAX_NODES, - MAX_DURATION + YELLOW_PERIOD + RED_PERIOD - 4, + PACKAGE_DURATION + YELLOW_PERIOD + RED_PERIOD - 4, sender=treasury, ) @@ -279,8 +385,8 @@ def test_process_ritual_extending( treasury, ritual_id, number_of_providers, DURATION, sender=treasury ) - erc20.approve(subscription.address, 10 * FEE, sender=adopter) - subscription.payForSubscription(sender=adopter) + erc20.approve(subscription.address, 10 * BASE_FEE, sender=adopter) + subscription.payForSubscription(0, sender=adopter) coordinator.setRitual( ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury ) @@ -348,13 +454,13 @@ def test_before_set_authorization( assert subscription.address == global_allow_list.feeModel() with ape.reverts("Only Access Controller can call this method"): - subscription.beforeSetAuthorization(0, [creator.address], True, sender=adopter) + subscription.beforeSetAuthorization(0, [creator], True, sender=adopter) with ape.reverts("Ritual must be active"): - global_allow_list.authorize(0, [creator.address], sender=adopter) + global_allow_list.authorize(0, [creator], sender=adopter) - erc20.approve(subscription.address, 10 * FEE, sender=adopter) - subscription.payForSubscription(sender=adopter) + erc20.approve(subscription.address, 10 * BASE_FEE, sender=adopter) + subscription.payForSubscription(0, sender=adopter) coordinator.setRitual( ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury ) @@ -363,17 +469,39 @@ def test_before_set_authorization( ) with ape.reverts("Ritual must be active"): - global_allow_list.authorize(0, [creator.address], sender=adopter) - global_allow_list.authorize(ritual_id, [creator.address], sender=adopter) + global_allow_list.authorize(0, [creator], sender=adopter) + + with ape.reverts("Encryptors slots filled up"): + global_allow_list.authorize(ritual_id, [creator], sender=adopter) + + subscription.payForEncryptorSlots(2, sender=adopter) + global_allow_list.authorize(ritual_id, [creator], sender=adopter) + assert subscription.usedEncryptorSlots() == 1 + + with ape.reverts("Encryptors slots filled up"): + global_allow_list.authorize(ritual_id, [creator, adopter], sender=adopter) + + global_allow_list.deauthorize(ritual_id, [creator], sender=adopter) + assert subscription.usedEncryptorSlots() == 0 + + global_allow_list.authorize(ritual_id, [creator, adopter], sender=adopter) + assert subscription.usedEncryptorSlots() == 2 end_subscription = subscription.getEndOfSubscription() chain.pending_timestamp = end_subscription + 1 with ape.reverts("Subscription has expired"): - global_allow_list.authorize(ritual_id, [creator.address], sender=adopter) + global_allow_list.authorize(ritual_id, [creator], sender=adopter) + + subscription.payForSubscription(0, sender=adopter) + with ape.reverts("Encryptors slots filled up"): + global_allow_list.authorize(ritual_id, [creator], sender=adopter) - subscription.payForSubscription(sender=adopter) - global_allow_list.authorize(ritual_id, [creator.address], sender=adopter) + subscription.payForEncryptorSlots(3, sender=adopter) + with ape.reverts("Encryptors slots filled up"): + global_allow_list.authorize(ritual_id, [treasury, subscription.address], sender=adopter) + global_allow_list.authorize(ritual_id, [treasury], sender=adopter) + assert subscription.usedEncryptorSlots() == 3 def test_before_is_authorized( @@ -396,8 +524,8 @@ def test_before_is_authorized( with ape.reverts("Ritual must be active"): global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) - erc20.approve(subscription.address, 10 * FEE, sender=adopter) - subscription.payForSubscription(sender=adopter) + erc20.approve(subscription.address, 10 * BASE_FEE, sender=adopter) + subscription.payForSubscription(1, sender=adopter) coordinator.setRitual( ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury ) @@ -414,5 +542,5 @@ def test_before_is_authorized( with ape.reverts("Yellow period has expired"): global_allow_list.isAuthorized(ritual_id, bytes(signature), bytes(data)) - subscription.payForSubscription(sender=adopter) + subscription.payForSubscription(0, sender=adopter) assert global_allow_list.isAuthorized(ritual_id, bytes(signature), bytes(data)) diff --git a/tests/test_managed_allow_list.py b/tests/test_managed_allow_list.py index 4e37c298d..3f5905cd0 100644 --- a/tests/test_managed_allow_list.py +++ b/tests/test_managed_allow_list.py @@ -148,6 +148,7 @@ def test_deauthorize( fee_token.approve(subscription.address, cost, sender=deployer) subscription.newSubscription(RITUAL_ID, sender=deployer) assert subscription.authorizationActionsCap(RITUAL_ID, admin) == 1000 + managed_allow_list.authorize(RITUAL_ID, [encryptor], sender=admin) with ape.reverts("Only administrator is permitted"): managed_allow_list.deauthorize(RITUAL_ID, [encryptor], sender=encryptor) From 82941ea79119a7ce58768139b1a8c6fa38132c71 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Thu, 20 Jun 2024 14:24:59 -0400 Subject: [PATCH 041/105] BqETHSubscription: initialize startOfSubscription during ritual initiation --- .../coordination/BqETHSubscription.sol | 35 +++++--- tests/test_bqeth_subscription.py | 84 +++++++++++++++---- 2 files changed, 90 insertions(+), 29 deletions(-) diff --git a/contracts/contracts/coordination/BqETHSubscription.sol b/contracts/contracts/coordination/BqETHSubscription.sol index 79cc1cb48..acff41ad6 100644 --- a/contracts/contracts/coordination/BqETHSubscription.sol +++ b/contracts/contracts/coordination/BqETHSubscription.sol @@ -29,7 +29,6 @@ contract BqETHSubscription is AbstractSubscription { uint256 public immutable baseFeeRate; uint256 public immutable encryptorFeeRate; - uint256 public immutable enryptorsFeeRate; uint256 public immutable maxNodes; IEncryptionAuthorizer public accessController; @@ -154,6 +153,10 @@ contract BqETHSubscription is AbstractSubscription { uint256 currentPeriodNumber = getCurrentPeriodNumber(); Billing storage billing = billingInfo[currentPeriodNumber]; + if (currentPeriodNumber == 0 && !billing.paid) { + return 0; + } + if (billing.paid) { while (billing.paid) { currentPeriodNumber++; @@ -202,9 +205,6 @@ contract BqETHSubscription is AbstractSubscription { uint256 fees = baseFees() + encryptorFees(encryptorSlots, packageDuration); uint256 periodNumber = currentPeriodNumber; - if (startOfSubscription == 0) { - startOfSubscription = uint32(block.timestamp); - } if (billingInfo[periodNumber].paid) { periodNumber++; } @@ -217,22 +217,28 @@ contract BqETHSubscription is AbstractSubscription { } /** - * @notice Pays for additional encryptor slots - * @param encryptorSlots Number of slots for encryptors + * @notice Pays for additional encryptor slots in the current period + * @param additionalEncryptorSlots Additional number of slots for encryptors */ - function payForEncryptorSlots(uint128 encryptorSlots) external { + function payForEncryptorSlots(uint128 additionalEncryptorSlots) external { uint256 currentPeriodNumber = getCurrentPeriodNumber(); Billing storage billing = billingInfo[currentPeriodNumber]; require(billing.paid, "Current billing period must be paid"); - uint32 endOfCurrentPeriod = uint32( - startOfSubscription + (currentPeriodNumber + 1) * packageDuration - ); - uint256 fees = encryptorFees(encryptorSlots, endOfCurrentPeriod - uint32(block.timestamp)); - billing.encryptorSlots += encryptorSlots; + uint32 duration = packageDuration; + uint32 endOfCurrentPeriod = 0; + if (startOfSubscription != 0) { + endOfCurrentPeriod = uint32( + startOfSubscription + (currentPeriodNumber + 1) * packageDuration + ); + duration = endOfCurrentPeriod - uint32(block.timestamp); + } + + uint256 fees = encryptorFees(additionalEncryptorSlots, duration); + billing.encryptorSlots += additionalEncryptorSlots; feeToken.safeTransferFrom(msg.sender, address(this), fees); - emit EncryptorSlotsPaid(msg.sender, fees, encryptorSlots, endOfCurrentPeriod); + emit EncryptorSlotsPaid(msg.sender, fees, additionalEncryptorSlots, endOfCurrentPeriod); } /** @@ -252,6 +258,9 @@ contract BqETHSubscription is AbstractSubscription { uint32 duration ) external override onlyCoordinator { require(initiator == adopter, "Only adopter can initiate ritual"); + if (startOfSubscription == 0) { + startOfSubscription = uint32(block.timestamp); + } uint32 endOfSubscription = getEndOfSubscription(); require(endOfSubscription != 0, "Subscription has to be paid first"); require( diff --git a/tests/test_bqeth_subscription.py b/tests/test_bqeth_subscription.py index 6bb12df42..0a53cdaa2 100644 --- a/tests/test_bqeth_subscription.py +++ b/tests/test_bqeth_subscription.py @@ -104,7 +104,9 @@ def global_allow_list(project, creator, coordinator, subscription, treasury): return contract -def test_pay_subscription(erc20, subscription, adopter, chain): +def test_pay_subscription( + erc20, subscription, coordinator, global_allow_list, adopter, treasury, chain +): erc20.approve(subscription.address, 10 * BASE_FEE, sender=adopter) # First payment @@ -116,9 +118,9 @@ def test_pay_subscription(erc20, subscription, adopter, chain): assert subscription.billingInfo(0) == (False, 0) tx = subscription.payForSubscription(0, sender=adopter) - timestamp = chain.pending_timestamp - 1 - assert subscription.startOfSubscription() == timestamp - assert subscription.getEndOfSubscription() == timestamp + PACKAGE_DURATION + end_subscription = 0 + assert subscription.startOfSubscription() == 0 + assert subscription.getEndOfSubscription() == 0 assert subscription.getCurrentPeriodNumber() == 0 assert subscription.billingInfo(0) == (True, 0) assert subscription.billingInfo(1) == (False, 0) @@ -132,7 +134,7 @@ def test_pay_subscription(erc20, subscription, adopter, chain): subscriber=adopter, amount=BASE_FEE, encryptorSlots=0, - endOfSubscription=timestamp + PACKAGE_DURATION, + endOfSubscription=end_subscription, ) ] @@ -140,8 +142,8 @@ def test_pay_subscription(erc20, subscription, adopter, chain): encryptor_slots = 10 encryptor_fees = ENCRYPTORS_FEE_RATE * PACKAGE_DURATION * encryptor_slots balance_before = erc20.balanceOf(adopter) - tx = subscription.payForSubscription(10, sender=adopter) - end_subscription = timestamp + 2 * PACKAGE_DURATION + tx = subscription.payForSubscription(encryptor_slots, sender=adopter) + end_subscription = 0 assert subscription.getEndOfSubscription() == end_subscription balance_after = erc20.balanceOf(adopter) assert balance_after + BASE_FEE + encryptor_fees == balance_before @@ -164,14 +166,56 @@ def test_pay_subscription(erc20, subscription, adopter, chain): with ape.reverts("Next billing period already paid"): subscription.payForSubscription(0, sender=adopter) + ritual_id = 1 + coordinator.setRitual( + ritual_id, + RitualState.DKG_AWAITING_TRANSCRIPTS, + 0, + global_allow_list.address, + sender=treasury, + ) + coordinator.processRitualPayment(adopter, ritual_id, MAX_NODES, DURATION, sender=treasury) + timestamp = chain.pending_timestamp - 1 + end_subscription = timestamp + 2 * PACKAGE_DURATION + assert subscription.startOfSubscription() == timestamp + assert subscription.getEndOfSubscription() == end_subscription + + chain.pending_timestamp = timestamp + PACKAGE_DURATION + 1 + + # Top up + balance_before = erc20.balanceOf(adopter) + tx = subscription.payForSubscription(encryptor_slots, sender=adopter) + end_subscription = timestamp + 3 * PACKAGE_DURATION + assert subscription.startOfSubscription() == timestamp + assert subscription.getEndOfSubscription() == end_subscription + balance_after = erc20.balanceOf(adopter) + assert balance_after + BASE_FEE + encryptor_fees == balance_before + assert erc20.balanceOf(subscription.address) == 3 * BASE_FEE + 2 * encryptor_fees + assert subscription.getCurrentPeriodNumber() == 1 + assert subscription.billingInfo(0) == (True, 0) + assert subscription.billingInfo(1) == (True, encryptor_slots) + assert subscription.billingInfo(2) == (True, encryptor_slots) + + events = subscription.SubscriptionPaid.from_receipt(tx) + assert events == [ + subscription.SubscriptionPaid( + subscriber=adopter, + amount=BASE_FEE + encryptor_fees, + encryptorSlots=encryptor_slots, + endOfSubscription=end_subscription, + ) + ] + # Can't pay after red period is over chain.pending_timestamp = end_subscription + YELLOW_PERIOD + RED_PERIOD + 1 - assert subscription.getCurrentPeriodNumber() == 2 + assert subscription.getCurrentPeriodNumber() == 3 with ape.reverts("Subscription is over"): subscription.payForSubscription(0, sender=adopter) -def test_pay_encryptor_slots(erc20, subscription, adopter, chain): +def test_pay_encryptor_slots( + erc20, subscription, coordinator, global_allow_list, adopter, treasury, chain +): encryptor_slots = 10 assert ( subscription.encryptorFees(encryptor_slots, PACKAGE_DURATION) @@ -184,17 +228,14 @@ def test_pay_encryptor_slots(erc20, subscription, adopter, chain): subscription.payForEncryptorSlots(encryptor_slots, sender=adopter) subscription.payForSubscription(encryptor_slots, sender=adopter) - timestamp = chain.pending_timestamp - 1 subscription.payForSubscription(0, sender=adopter) assert subscription.billingInfo(0) == (True, encryptor_slots) assert subscription.billingInfo(1) == (True, 0) duration = PACKAGE_DURATION // 3 - chain.pending_timestamp = timestamp + duration - encryptor_fees = encryptor_slots * (PACKAGE_DURATION - duration) * ENCRYPTORS_FEE_RATE - assert ( - subscription.encryptorFees(encryptor_slots, PACKAGE_DURATION - duration) == encryptor_fees - ) + chain.pending_timestamp += duration + encryptor_fees = encryptor_slots * PACKAGE_DURATION * ENCRYPTORS_FEE_RATE + assert subscription.encryptorFees(encryptor_slots, PACKAGE_DURATION) == encryptor_fees adopter_balance_before = erc20.balanceOf(adopter) subscription_balance_before = erc20.balanceOf(subscription.address) @@ -212,10 +253,21 @@ def test_pay_encryptor_slots(erc20, subscription, adopter, chain): subscriber=adopter, amount=encryptor_fees, encryptorSlots=encryptor_slots, - endOfCurrentPeriod=timestamp + PACKAGE_DURATION, + endOfCurrentPeriod=0, ) ] + ritual_id = 1 + coordinator.setRitual( + ritual_id, + RitualState.DKG_AWAITING_TRANSCRIPTS, + 0, + global_allow_list.address, + sender=treasury, + ) + coordinator.processRitualPayment(adopter, ritual_id, MAX_NODES, DURATION, sender=treasury) + timestamp = chain.pending_timestamp - 1 + duration = PACKAGE_DURATION // 5 chain.pending_timestamp = timestamp + PACKAGE_DURATION + duration encryptor_fees = encryptor_slots * (PACKAGE_DURATION - duration) * ENCRYPTORS_FEE_RATE From 500d57e62b923537d1011bd918caee11621bec56 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Mon, 24 Jun 2024 16:20:36 -0400 Subject: [PATCH 042/105] BqETHSubscription: force to pay for used encryptor slots --- contracts/contracts/coordination/BqETHSubscription.sol | 10 ++++++++++ tests/test_bqeth_subscription.py | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/contracts/contracts/coordination/BqETHSubscription.sol b/contracts/contracts/coordination/BqETHSubscription.sol index acff41ad6..59d535847 100644 --- a/contracts/contracts/coordination/BqETHSubscription.sol +++ b/contracts/contracts/coordination/BqETHSubscription.sol @@ -303,4 +303,14 @@ contract BqETHSubscription is AbstractSubscription { usedEncryptorSlots -= addresses.length; } } + + function beforeIsAuthorized(uint32 ritualId) public view override { + super.beforeIsAuthorized(ritualId); + // used encryptor slots must be paid + if (block.timestamp <= getEndOfSubscription()) { + uint256 currentPeriodNumber = getCurrentPeriodNumber(); + Billing storage billing = billingInfo[currentPeriodNumber]; + require(usedEncryptorSlots <= billing.encryptorSlots, "Encryptors slots filled up"); + } + } } diff --git a/tests/test_bqeth_subscription.py b/tests/test_bqeth_subscription.py index 0a53cdaa2..54de86eca 100644 --- a/tests/test_bqeth_subscription.py +++ b/tests/test_bqeth_subscription.py @@ -595,4 +595,8 @@ def test_before_is_authorized( global_allow_list.isAuthorized(ritual_id, bytes(signature), bytes(data)) subscription.payForSubscription(0, sender=adopter) + with ape.reverts("Encryptors slots filled up"): + global_allow_list.isAuthorized(ritual_id, bytes(signature), bytes(data)) + + subscription.payForEncryptorSlots(1, sender=adopter) assert global_allow_list.isAuthorized(ritual_id, bytes(signature), bytes(data)) From a739b0773f9de37bac587530425511a0f7cfbd18 Mon Sep 17 00:00:00 2001 From: Victoria Date: Tue, 25 Jun 2024 14:51:09 +0200 Subject: [PATCH 043/105] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Núñez --- .../coordination/AbstractSubscription.sol | 8 +++--- .../coordination/BqETHSubscription.sol | 28 ++++++++++--------- tests/test_bqeth_subscription.py | 4 +-- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/contracts/contracts/coordination/AbstractSubscription.sol b/contracts/contracts/coordination/AbstractSubscription.sol index 5ef7deced..14361c08e 100644 --- a/contracts/contracts/coordination/AbstractSubscription.sol +++ b/contracts/contracts/coordination/AbstractSubscription.sol @@ -12,7 +12,7 @@ import "./IFeeModel.sol"; abstract contract AbstractSubscription is IFeeModel { Coordinator public immutable coordinator; - uint32 public immutable packageDuration; + uint32 public immutable subscriptionPeriodDuration; uint32 public immutable yellowPeriodDuration; uint32 public immutable redPeriodDuration; @@ -20,19 +20,19 @@ abstract contract AbstractSubscription is IFeeModel { * @notice Sets subscription parameters * @dev The coordinator and fee token contracts cannot be zero addresses * @param _coordinator The address of the coordinator contract - * @param _packageDuration Maximum duration of ritual + * @param _subscriptionPeriodDuration Maximum duration of subscription period * @param _yellowPeriodDuration Duration of yellow period * @param _redPeriodDuration Duration of red period */ constructor( Coordinator _coordinator, - uint32 _packageDuration, + uint32 _subscriptionPeriodDuration, uint32 _yellowPeriodDuration, uint32 _redPeriodDuration ) { require(address(_coordinator) != address(0), "Coordinator cannot be the zero address"); coordinator = _coordinator; - packageDuration = _packageDuration; + subscriptionPeriodDuration = _subscriptionPeriodDuration; yellowPeriodDuration = _yellowPeriodDuration; redPeriodDuration = _redPeriodDuration; } diff --git a/contracts/contracts/coordination/BqETHSubscription.sol b/contracts/contracts/coordination/BqETHSubscription.sol index 59d535847..aff601c26 100644 --- a/contracts/contracts/coordination/BqETHSubscription.sol +++ b/contracts/contracts/coordination/BqETHSubscription.sol @@ -61,14 +61,14 @@ contract BqETHSubscription is AbstractSubscription { ); /** - * @notice Emitted when a subscription is paid - * @param subscriber The address of the subscriber + * @notice Emitted when additional encryptor slots are paid + * @param sponsor The address that paid for the slots * @param amount The amount paid * @param encryptorSlots Number of encryptor slots * @param endOfCurrentPeriod End timestamp of the current billing period */ event EncryptorSlotsPaid( - address indexed subscriber, + address indexed sponsor, uint256 amount, uint128 encryptorSlots, uint32 endOfCurrentPeriod @@ -84,7 +84,7 @@ contract BqETHSubscription is AbstractSubscription { * @param _baseFeeRate Fee rate per node per second * @param _encryptorFeeRate Fee rate per encryptor per second * @param _maxNodes Maximum nodes in the package - * @param _packageDuration Maximum duration of ritual + * @param _subscriptionPeriodDuration Maximum duration of subscription period * @param _yellowPeriodDuration Duration of yellow period * @param _redPeriodDuration Duration of red period */ @@ -96,13 +96,13 @@ contract BqETHSubscription is AbstractSubscription { uint256 _baseFeeRate, uint256 _encryptorFeeRate, uint256 _maxNodes, - uint32 _packageDuration, + uint32 _subscriptionPeriodDuration, uint32 _yellowPeriodDuration, uint32 _redPeriodDuration ) AbstractSubscription( _coordinator, - _packageDuration, + _subscriptionPeriodDuration, _yellowPeriodDuration, _redPeriodDuration ) @@ -143,7 +143,7 @@ contract BqETHSubscription is AbstractSubscription { if (startOfSubscription == 0) { return 0; } - return (block.timestamp - startOfSubscription) / packageDuration; + return (block.timestamp - startOfSubscription) / subscriptionPeriodDuration; } function getEndOfSubscription() public view override returns (uint32 endOfSubscription) { @@ -169,7 +169,9 @@ contract BqETHSubscription is AbstractSubscription { } currentPeriodNumber++; } - endOfSubscription = uint32(startOfSubscription + currentPeriodNumber * packageDuration); + endOfSubscription = uint32( + startOfSubscription + currentPeriodNumber * subscriptionPeriodDuration + ); } function initialize(IEncryptionAuthorizer _accessController) external { @@ -182,7 +184,7 @@ contract BqETHSubscription is AbstractSubscription { } function baseFees() public view returns (uint256) { - return baseFeeRate * packageDuration * maxNodes; + return baseFeeRate * subscriptionPeriodDuration * maxNodes; } function encryptorFees(uint128 encryptorSlots, uint32 duration) public view returns (uint256) { @@ -190,7 +192,7 @@ contract BqETHSubscription is AbstractSubscription { } /** - * @notice Pays for a subscription + * @notice Pays for the closest unpaid subscription period (either the current or the next) * @param encryptorSlots Number of slots for encryptors */ function payForSubscription(uint128 encryptorSlots) external { @@ -203,7 +205,7 @@ contract BqETHSubscription is AbstractSubscription { "Subscription is over" ); - uint256 fees = baseFees() + encryptorFees(encryptorSlots, packageDuration); + uint256 fees = baseFees() + encryptorFees(encryptorSlots, subscriptionPeriodDuration); uint256 periodNumber = currentPeriodNumber; if (billingInfo[periodNumber].paid) { periodNumber++; @@ -225,11 +227,11 @@ contract BqETHSubscription is AbstractSubscription { Billing storage billing = billingInfo[currentPeriodNumber]; require(billing.paid, "Current billing period must be paid"); - uint32 duration = packageDuration; + uint32 duration = subscriptionPeriodDuration; uint32 endOfCurrentPeriod = 0; if (startOfSubscription != 0) { endOfCurrentPeriod = uint32( - startOfSubscription + (currentPeriodNumber + 1) * packageDuration + startOfSubscription + (currentPeriodNumber + 1) * subscriptionPeriodDuration ); duration = endOfCurrentPeriod - uint32(block.timestamp); } diff --git a/tests/test_bqeth_subscription.py b/tests/test_bqeth_subscription.py index 54de86eca..02bb45b5c 100644 --- a/tests/test_bqeth_subscription.py +++ b/tests/test_bqeth_subscription.py @@ -250,7 +250,7 @@ def test_pay_encryptor_slots( events = subscription.EncryptorSlotsPaid.from_receipt(tx) assert events == [ subscription.EncryptorSlotsPaid( - subscriber=adopter, + sponsor=adopter, amount=encryptor_fees, encryptorSlots=encryptor_slots, endOfCurrentPeriod=0, @@ -285,7 +285,7 @@ def test_pay_encryptor_slots( events = subscription.EncryptorSlotsPaid.from_receipt(tx) assert events == [ subscription.EncryptorSlotsPaid( - subscriber=adopter, + sponsor=adopter, amount=encryptor_fees, encryptorSlots=encryptor_slots, endOfCurrentPeriod=timestamp + 2 * PACKAGE_DURATION, From f9597e0ab7dd612a75b184e6d66e349d8dc0f6bd Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Tue, 18 Jun 2024 12:33:16 -0400 Subject: [PATCH 044/105] BqETHSubscription: upgradability --- .../coordination/AbstractSubscription.sol | 2 ++ .../coordination/BqETHSubscription.sol | 34 +++++++++---------- tests/test_bqeth_subscription.py | 22 ++++++++---- 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/contracts/contracts/coordination/AbstractSubscription.sol b/contracts/contracts/coordination/AbstractSubscription.sol index 14361c08e..3201a21aa 100644 --- a/contracts/contracts/coordination/AbstractSubscription.sol +++ b/contracts/contracts/coordination/AbstractSubscription.sol @@ -16,6 +16,8 @@ abstract contract AbstractSubscription is IFeeModel { uint32 public immutable yellowPeriodDuration; uint32 public immutable redPeriodDuration; + uint256[20] private gap; + /** * @notice Sets subscription parameters * @dev The coordinator and fee token contracts cannot be zero addresses diff --git a/contracts/contracts/coordination/BqETHSubscription.sol b/contracts/contracts/coordination/BqETHSubscription.sol index aff601c26..7f825a777 100644 --- a/contracts/contracts/coordination/BqETHSubscription.sol +++ b/contracts/contracts/coordination/BqETHSubscription.sol @@ -4,13 +4,15 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol"; +import "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; import "./AbstractSubscription.sol"; /** * @title BqETH Subscription * @notice Manages the subscription information for rituals. */ -contract BqETHSubscription is AbstractSubscription { +contract BqETHSubscription is AbstractSubscription, Initializable, OwnableUpgradeable { using SafeERC20 for IERC20; struct Billing { @@ -22,9 +24,6 @@ contract BqETHSubscription is AbstractSubscription { uint32 public constant INACTIVE_RITUAL_ID = type(uint32).max; - // TODO: DAO Treasury - // TODO: Should it be updatable? - address public immutable beneficiary; address public immutable adopter; uint256 public immutable baseFeeRate; @@ -39,6 +38,8 @@ contract BqETHSubscription is AbstractSubscription { uint256 public usedEncryptorSlots; mapping(uint256 periodNumber => Billing billing) public billingInfo; + uint256[20] private gap; + /** * @notice Emitted when a subscription is spent * @param beneficiary The address of the beneficiary @@ -79,7 +80,6 @@ contract BqETHSubscription is AbstractSubscription { * @dev The coordinator and fee token contracts cannot be zero addresses * @param _coordinator The address of the coordinator contract * @param _feeToken The address of the fee token contract - * @param _beneficiary The address of the beneficiary * @param _adopter The address of the adopter * @param _baseFeeRate Fee rate per node per second * @param _encryptorFeeRate Fee rate per encryptor per second @@ -91,7 +91,6 @@ contract BqETHSubscription is AbstractSubscription { constructor( Coordinator _coordinator, IERC20 _feeToken, - address _beneficiary, address _adopter, uint256 _baseFeeRate, uint256 _encryptorFeeRate, @@ -108,19 +107,13 @@ contract BqETHSubscription is AbstractSubscription { ) { require(address(_feeToken) != address(0), "Fee token cannot be the zero address"); - require(_beneficiary != address(0), "Beneficiary cannot be the zero address"); require(_adopter != address(0), "Adopter cannot be the zero address"); feeToken = _feeToken; - beneficiary = _beneficiary; adopter = _adopter; baseFeeRate = _baseFeeRate; encryptorFeeRate = _encryptorFeeRate; maxNodes = _maxNodes; - } - - modifier onlyBeneficiary() { - require(msg.sender == beneficiary, "Only the beneficiary can call this method"); - _; + _disableInitializers(); } modifier onlyAccessController() override { @@ -174,13 +167,20 @@ contract BqETHSubscription is AbstractSubscription { ); } - function initialize(IEncryptionAuthorizer _accessController) external { + /** + * @notice Initialize function for using with OpenZeppelin proxy + */ + function initialize( + address _beneficiary, + IEncryptionAuthorizer _accessController + ) external initializer { require( address(accessController) == address(0) && address(_accessController) != address(0), "Access controller not already set and parameter cannot be the zero address" ); accessController = _accessController; activeRitualId = INACTIVE_RITUAL_ID; + __Ownable_init(_beneficiary); } function baseFees() public view returns (uint256) { @@ -247,10 +247,10 @@ contract BqETHSubscription is AbstractSubscription { * @notice Withdraws the contract balance to the beneficiary * @param amount The amount to withdraw */ - function withdrawToBeneficiary(uint256 amount) external onlyBeneficiary { + function withdrawToBeneficiary(uint256 amount) external onlyOwner { require(amount <= feeToken.balanceOf(address(this)), "Insufficient balance available"); - feeToken.safeTransfer(beneficiary, amount); - emit WithdrawalToBeneficiary(beneficiary, amount); + feeToken.safeTransfer(msg.sender, amount); + emit WithdrawalToBeneficiary(msg.sender, amount); } function processRitualPayment( diff --git a/tests/test_bqeth_subscription.py b/tests/test_bqeth_subscription.py index 02bb45b5c..8d85a1bee 100644 --- a/tests/test_bqeth_subscription.py +++ b/tests/test_bqeth_subscription.py @@ -77,11 +77,10 @@ def coordinator(project, creator): @pytest.fixture() -def subscription(project, creator, coordinator, erc20, treasury, adopter): +def subscription(project, creator, coordinator, erc20, adopter, oz_dependency): contract = project.BqETHSubscription.deploy( coordinator.address, erc20.address, - treasury, adopter, BASE_FEE_RATE, ENCRYPTORS_FEE_RATE, @@ -91,8 +90,17 @@ def subscription(project, creator, coordinator, erc20, treasury, adopter): RED_PERIOD, sender=creator, ) - coordinator.setFeeModel(contract.address, sender=creator) - return contract + + encoded_initializer_function = b"" + proxy = oz_dependency.TransparentUpgradeableProxy.deploy( + contract.address, + creator, + encoded_initializer_function, + sender=creator, + ) + proxy_contract = project.BqETHSubscription.at(proxy.address) + coordinator.setFeeModel(proxy_contract.address, sender=creator) + return proxy_contract @pytest.fixture() @@ -100,7 +108,7 @@ def global_allow_list(project, creator, coordinator, subscription, treasury): contract = project.GlobalAllowList.deploy( coordinator.address, subscription.address, sender=creator ) - subscription.initialize(contract.address, sender=treasury) + subscription.initialize(treasury.address, contract.address, sender=treasury) return contract @@ -297,10 +305,10 @@ def test_pay_encryptor_slots( subscription.payForEncryptorSlots(encryptor_slots, sender=adopter) -def test_withdraw(erc20, subscription, adopter, treasury): +def test_withdraw(erc20, subscription, adopter, treasury, global_allow_list): erc20.approve(subscription.address, 10 * BASE_FEE, sender=adopter) - with ape.reverts("Only the beneficiary can call this method"): + with ape.reverts(): subscription.withdrawToBeneficiary(1, sender=adopter) with ape.reverts("Insufficient balance available"): From 679a5cbedb8c63cb624e1eff8f96834441749d7b Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Wed, 19 Jun 2024 13:45:48 -0400 Subject: [PATCH 045/105] BqETHSubscription: rename beneficiary to treasury --- .../contracts/coordination/BqETHSubscription.sol | 14 +++++++------- tests/test_bqeth_subscription.py | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/contracts/contracts/coordination/BqETHSubscription.sol b/contracts/contracts/coordination/BqETHSubscription.sol index 7f825a777..a1a8baf70 100644 --- a/contracts/contracts/coordination/BqETHSubscription.sol +++ b/contracts/contracts/coordination/BqETHSubscription.sol @@ -42,10 +42,10 @@ contract BqETHSubscription is AbstractSubscription, Initializable, OwnableUpgrad /** * @notice Emitted when a subscription is spent - * @param beneficiary The address of the beneficiary + * @param treasury The address of the treasury * @param amount The amount withdrawn */ - event WithdrawalToBeneficiary(address indexed beneficiary, uint256 amount); + event WithdrawalToTreasury(address indexed treasury, uint256 amount); /** * @notice Emitted when a subscription is paid @@ -171,7 +171,7 @@ contract BqETHSubscription is AbstractSubscription, Initializable, OwnableUpgrad * @notice Initialize function for using with OpenZeppelin proxy */ function initialize( - address _beneficiary, + address _treasury, IEncryptionAuthorizer _accessController ) external initializer { require( @@ -180,7 +180,7 @@ contract BqETHSubscription is AbstractSubscription, Initializable, OwnableUpgrad ); accessController = _accessController; activeRitualId = INACTIVE_RITUAL_ID; - __Ownable_init(_beneficiary); + __Ownable_init(_treasury); } function baseFees() public view returns (uint256) { @@ -244,13 +244,13 @@ contract BqETHSubscription is AbstractSubscription, Initializable, OwnableUpgrad } /** - * @notice Withdraws the contract balance to the beneficiary + * @notice Withdraws the contract balance to the treasury * @param amount The amount to withdraw */ - function withdrawToBeneficiary(uint256 amount) external onlyOwner { + function withdrawToTreasury(uint256 amount) external onlyOwner { require(amount <= feeToken.balanceOf(address(this)), "Insufficient balance available"); feeToken.safeTransfer(msg.sender, amount); - emit WithdrawalToBeneficiary(msg.sender, amount); + emit WithdrawalToTreasury(msg.sender, amount); } function processRitualPayment( diff --git a/tests/test_bqeth_subscription.py b/tests/test_bqeth_subscription.py index 8d85a1bee..b9839052c 100644 --- a/tests/test_bqeth_subscription.py +++ b/tests/test_bqeth_subscription.py @@ -309,22 +309,22 @@ def test_withdraw(erc20, subscription, adopter, treasury, global_allow_list): erc20.approve(subscription.address, 10 * BASE_FEE, sender=adopter) with ape.reverts(): - subscription.withdrawToBeneficiary(1, sender=adopter) + subscription.withdrawToTreasury(1, sender=adopter) with ape.reverts("Insufficient balance available"): - subscription.withdrawToBeneficiary(1, sender=treasury) + subscription.withdrawToTreasury(1, sender=treasury) subscription.payForSubscription(0, sender=adopter) with ape.reverts("Insufficient balance available"): - subscription.withdrawToBeneficiary(BASE_FEE + 1, sender=treasury) + subscription.withdrawToTreasury(BASE_FEE + 1, sender=treasury) - tx = subscription.withdrawToBeneficiary(BASE_FEE, sender=treasury) + tx = subscription.withdrawToTreasury(BASE_FEE, sender=treasury) assert erc20.balanceOf(treasury) == BASE_FEE assert erc20.balanceOf(subscription.address) == 0 - events = subscription.WithdrawalToBeneficiary.from_receipt(tx) - assert events == [subscription.WithdrawalToBeneficiary(beneficiary=treasury, amount=BASE_FEE)] + events = subscription.WithdrawalToTreasury.from_receipt(tx) + assert events == [subscription.WithdrawalToTreasury(treasury=treasury, amount=BASE_FEE)] def test_process_ritual_payment( From 5884fcca739bab063e47d7ca8b0f3a9c2bc611bb Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Wed, 26 Jun 2024 10:25:09 -0400 Subject: [PATCH 046/105] Subscription: extract encryptor slots logic to abstract contracts --- .../coordination/ManagedAllowList.sol | 2 +- .../AbstractSubscription.sol | 4 +- .../{ => subscription}/BqETHSubscription.sol | 81 ++----------- .../EncryptorSlotsSubscription.sol | 106 ++++++++++++++++++ .../{ => subscription}/Subscription.sol | 4 +- 5 files changed, 123 insertions(+), 74 deletions(-) rename contracts/contracts/coordination/{ => subscription}/AbstractSubscription.sol (98%) rename contracts/contracts/coordination/{ => subscription}/BqETHSubscription.sol (78%) create mode 100644 contracts/contracts/coordination/subscription/EncryptorSlotsSubscription.sol rename contracts/contracts/coordination/{ => subscription}/Subscription.sol (99%) diff --git a/contracts/contracts/coordination/ManagedAllowList.sol b/contracts/contracts/coordination/ManagedAllowList.sol index 67cea1621..547c8d451 100644 --- a/contracts/contracts/coordination/ManagedAllowList.sol +++ b/contracts/contracts/coordination/ManagedAllowList.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; import "../lib/LookupKey.sol"; import "./GlobalAllowList.sol"; import "./Coordinator.sol"; -import {UpfrontSubscriptionWithEncryptorsCap} from "./Subscription.sol"; +import {UpfrontSubscriptionWithEncryptorsCap} from "./subscription/Subscription.sol"; /** * @title ManagedAllowList diff --git a/contracts/contracts/coordination/AbstractSubscription.sol b/contracts/contracts/coordination/subscription/AbstractSubscription.sol similarity index 98% rename from contracts/contracts/coordination/AbstractSubscription.sol rename to contracts/contracts/coordination/subscription/AbstractSubscription.sol index 3201a21aa..b1febfb52 100644 --- a/contracts/contracts/coordination/AbstractSubscription.sol +++ b/contracts/contracts/coordination/subscription/AbstractSubscription.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; -import "./Coordinator.sol"; -import "./IFeeModel.sol"; +import "../Coordinator.sol"; +import "../IFeeModel.sol"; /** * @title Base Subscription contract diff --git a/contracts/contracts/coordination/BqETHSubscription.sol b/contracts/contracts/coordination/subscription/BqETHSubscription.sol similarity index 78% rename from contracts/contracts/coordination/BqETHSubscription.sol rename to contracts/contracts/coordination/subscription/BqETHSubscription.sol index a1a8baf70..b67c074c1 100644 --- a/contracts/contracts/coordination/BqETHSubscription.sol +++ b/contracts/contracts/coordination/subscription/BqETHSubscription.sol @@ -6,13 +6,13 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; -import "./AbstractSubscription.sol"; +import "./EncryptorSlotsSubscription.sol"; /** * @title BqETH Subscription * @notice Manages the subscription information for rituals. */ -contract BqETHSubscription is AbstractSubscription, Initializable, OwnableUpgradeable { +contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, OwnableUpgradeable { using SafeERC20 for IERC20; struct Billing { @@ -31,11 +31,7 @@ contract BqETHSubscription is AbstractSubscription, Initializable, OwnableUpgrad uint256 public immutable maxNodes; IEncryptionAuthorizer public accessController; - - uint32 public startOfSubscription; uint32 public activeRitualId; - - uint256 public usedEncryptorSlots; mapping(uint256 periodNumber => Billing billing) public billingInfo; uint256[20] private gap; @@ -99,7 +95,7 @@ contract BqETHSubscription is AbstractSubscription, Initializable, OwnableUpgrad uint32 _yellowPeriodDuration, uint32 _redPeriodDuration ) - AbstractSubscription( + EncryptorSlotsSubscription( _coordinator, _subscriptionPeriodDuration, _yellowPeriodDuration, @@ -132,41 +128,6 @@ contract BqETHSubscription is AbstractSubscription, Initializable, OwnableUpgrad _; } - function getCurrentPeriodNumber() public view returns (uint256) { - if (startOfSubscription == 0) { - return 0; - } - return (block.timestamp - startOfSubscription) / subscriptionPeriodDuration; - } - - function getEndOfSubscription() public view override returns (uint32 endOfSubscription) { - if (startOfSubscription == 0) { - return 0; - } - - uint256 currentPeriodNumber = getCurrentPeriodNumber(); - Billing storage billing = billingInfo[currentPeriodNumber]; - if (currentPeriodNumber == 0 && !billing.paid) { - return 0; - } - - if (billing.paid) { - while (billing.paid) { - currentPeriodNumber++; - billing = billingInfo[currentPeriodNumber]; - } - } else { - while (!billing.paid) { - currentPeriodNumber--; - billing = billingInfo[currentPeriodNumber]; - } - currentPeriodNumber++; - } - endOfSubscription = uint32( - startOfSubscription + currentPeriodNumber * subscriptionPeriodDuration - ); - } - /** * @notice Initialize function for using with OpenZeppelin proxy */ @@ -191,7 +152,16 @@ contract BqETHSubscription is AbstractSubscription, Initializable, OwnableUpgrad return encryptorFeeRate * duration * encryptorSlots; } + function isPeriodPaid(uint256 periodNumber) public view override returns (bool) { + return billingInfo[periodNumber].paid; + } + + function getPaidEncryptorSlots(uint256 periodNumber) public view override returns (uint256) { + return billingInfo[periodNumber].encryptorSlots; + } + /** + * * @notice Pays for the closest unpaid subscription period (either the current or the next) * @param encryptorSlots Number of slots for encryptors */ @@ -288,31 +258,4 @@ contract BqETHSubscription is AbstractSubscription, Initializable, OwnableUpgrad } activeRitualId = ritualId; } - - function beforeSetAuthorization( - uint32 ritualId, - address[] calldata addresses, - bool value - ) public override { - super.beforeSetAuthorization(ritualId, addresses, value); - if (value) { - uint256 currentPeriodNumber = getCurrentPeriodNumber(); - Billing storage billing = billingInfo[currentPeriodNumber]; - uint128 encryptorSlots = billing.paid ? billing.encryptorSlots : 0; - usedEncryptorSlots += addresses.length; - require(usedEncryptorSlots <= encryptorSlots, "Encryptors slots filled up"); - } else { - usedEncryptorSlots -= addresses.length; - } - } - - function beforeIsAuthorized(uint32 ritualId) public view override { - super.beforeIsAuthorized(ritualId); - // used encryptor slots must be paid - if (block.timestamp <= getEndOfSubscription()) { - uint256 currentPeriodNumber = getCurrentPeriodNumber(); - Billing storage billing = billingInfo[currentPeriodNumber]; - require(usedEncryptorSlots <= billing.encryptorSlots, "Encryptors slots filled up"); - } - } } diff --git a/contracts/contracts/coordination/subscription/EncryptorSlotsSubscription.sol b/contracts/contracts/coordination/subscription/EncryptorSlotsSubscription.sol new file mode 100644 index 000000000..8522fd203 --- /dev/null +++ b/contracts/contracts/coordination/subscription/EncryptorSlotsSubscription.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.0; + +import "./AbstractSubscription.sol"; + +/** + * @title Subscription that includes payment for enryptor slots + * @notice Manages the subscription information for rituals. + */ +abstract contract EncryptorSlotsSubscription is AbstractSubscription { + uint32 public startOfSubscription; + uint256 public usedEncryptorSlots; + // example of storage layout + // mapping(uint256 periodNumber => Billing billing) public billingInfo; + + uint256[20] private gap; + + /** + * @notice Sets the coordinator and fee token contracts + * @dev The coordinator and fee token contracts cannot be zero addresses + * @param _coordinator The address of the coordinator contract + * @param _subscriptionPeriodDuration Maximum duration of subscription period + * @param _yellowPeriodDuration Duration of yellow period + * @param _redPeriodDuration Duration of red period + */ + constructor( + Coordinator _coordinator, + uint32 _subscriptionPeriodDuration, + uint32 _yellowPeriodDuration, + uint32 _redPeriodDuration + ) + AbstractSubscription( + _coordinator, + _subscriptionPeriodDuration, + _yellowPeriodDuration, + _redPeriodDuration + ) + {} + + function isPeriodPaid(uint256 periodNumber) public view virtual returns (bool); + + function getPaidEncryptorSlots(uint256 periodNumber) public view virtual returns (uint256); + + function getCurrentPeriodNumber() public view returns (uint256) { + if (startOfSubscription == 0) { + return 0; + } + return (block.timestamp - startOfSubscription) / subscriptionPeriodDuration; + } + + function getEndOfSubscription() public view override returns (uint32 endOfSubscription) { + if (startOfSubscription == 0) { + return 0; + } + + uint256 currentPeriodNumber = getCurrentPeriodNumber(); + if (currentPeriodNumber == 0 && !isPeriodPaid(currentPeriodNumber)) { + return 0; + } + + if (isPeriodPaid(currentPeriodNumber)) { + while (isPeriodPaid(currentPeriodNumber)) { + currentPeriodNumber++; + } + } else { + while (!isPeriodPaid(currentPeriodNumber)) { + currentPeriodNumber--; + } + currentPeriodNumber++; + } + endOfSubscription = uint32( + startOfSubscription + currentPeriodNumber * subscriptionPeriodDuration + ); + } + + function beforeSetAuthorization( + uint32 ritualId, + address[] calldata addresses, + bool value + ) public virtual override { + super.beforeSetAuthorization(ritualId, addresses, value); + if (value) { + uint256 currentPeriodNumber = getCurrentPeriodNumber(); + uint256 encryptorSlots = isPeriodPaid(currentPeriodNumber) + ? getPaidEncryptorSlots(currentPeriodNumber) + : 0; + usedEncryptorSlots += addresses.length; + require(usedEncryptorSlots <= encryptorSlots, "Encryptors slots filled up"); + } else { + usedEncryptorSlots -= addresses.length; + } + } + + function beforeIsAuthorized(uint32 ritualId) public view virtual override { + super.beforeIsAuthorized(ritualId); + // used encryptor slots must be paid + if (block.timestamp <= getEndOfSubscription()) { + uint256 currentPeriodNumber = getCurrentPeriodNumber(); + require( + usedEncryptorSlots <= getPaidEncryptorSlots(currentPeriodNumber), + "Encryptors slots filled up" + ); + } + } +} diff --git a/contracts/contracts/coordination/Subscription.sol b/contracts/contracts/coordination/subscription/Subscription.sol similarity index 99% rename from contracts/contracts/coordination/Subscription.sol rename to contracts/contracts/coordination/subscription/Subscription.sol index 65252762c..7111a7d93 100644 --- a/contracts/contracts/coordination/Subscription.sol +++ b/contracts/contracts/coordination/subscription/Subscription.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "../lib/LookupKey.sol"; -import "./Coordinator.sol"; +import "../../lib/LookupKey.sol"; +import "../Coordinator.sol"; using SafeERC20 for IERC20; From d10073e5a1001320a39938366735ae4e6a492119 Mon Sep 17 00:00:00 2001 From: Victoria Date: Thu, 27 Jun 2024 19:14:16 +0200 Subject: [PATCH 047/105] Apply suggestions from code review Co-authored-by: Derek Pierre --- contracts/contracts/coordination/Coordinator.sol | 4 ++-- .../contracts/coordination/subscription/BqETHSubscription.sol | 2 +- tests/test_bqeth_subscription.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/contracts/coordination/Coordinator.sol b/contracts/contracts/coordination/Coordinator.sol index ce970e765..9be31fb9d 100644 --- a/contracts/contracts/coordination/Coordinator.sol +++ b/contracts/contracts/coordination/Coordinator.sol @@ -43,7 +43,7 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable BLS12381.G2Point publicKey ); event FeeModelApproved(IFeeModel feeModel); - event RitualExtended(uint32 indexed ritualId, uint32 indexed duration); + event RitualExtended(uint32 indexed ritualId, uint32 endTimestamp); enum RitualState { NON_INITIATED, @@ -577,6 +577,6 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable ritual.participant.length, duration ); - emit RitualExtended(ritualId, duration); + emit RitualExtended(ritualId, ritual.endTimestamp); } } diff --git a/contracts/contracts/coordination/subscription/BqETHSubscription.sol b/contracts/contracts/coordination/subscription/BqETHSubscription.sol index b67c074c1..085782a6b 100644 --- a/contracts/contracts/coordination/subscription/BqETHSubscription.sol +++ b/contracts/contracts/coordination/subscription/BqETHSubscription.sol @@ -253,7 +253,7 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable state == Coordinator.RitualState.DKG_INVALID || state == Coordinator.RitualState.DKG_TIMEOUT || state == Coordinator.RitualState.EXPIRED, // TODO check if it's ok - "Only failed rituals allowed to be reinitiated" + "Only failed/expired rituals allowed to be reinitiated" ); } activeRitualId = ritualId; diff --git a/tests/test_bqeth_subscription.py b/tests/test_bqeth_subscription.py index b9839052c..d47ecbc6c 100644 --- a/tests/test_bqeth_subscription.py +++ b/tests/test_bqeth_subscription.py @@ -390,7 +390,7 @@ def test_process_ritual_payment( coordinator.setRitual( new_ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury ) - with ape.reverts("Only failed rituals allowed to be reinitiated"): + with ape.reverts("Only failed/expired rituals allowed to be reinitiated"): coordinator.processRitualPayment( adopter, new_ritual_id, number_of_providers, DURATION, sender=treasury ) From b3a3a94366337c54a3c6de0f77764a19220ae7d8 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Thu, 27 Jun 2024 13:20:33 -0400 Subject: [PATCH 048/105] Removes BetaProgramInitiator --- .../coordination/BetaProgramInitiator.sol | 167 ------- .../contracts/coordination/Coordinator.sol | 18 +- .../test/BetaProgramInitiatorTestSet.sol | 75 --- scripts/grant_initiator_role.py | 47 -- tests/test_beta_program_initiator.py | 465 ------------------ tests/test_coordinator.py | 8 - tests/test_global_allow_list.py | 2 - 7 files changed, 4 insertions(+), 778 deletions(-) delete mode 100644 contracts/contracts/coordination/BetaProgramInitiator.sol delete mode 100644 contracts/test/BetaProgramInitiatorTestSet.sol delete mode 100644 scripts/grant_initiator_role.py delete mode 100644 tests/test_beta_program_initiator.py diff --git a/contracts/contracts/coordination/BetaProgramInitiator.sol b/contracts/contracts/coordination/BetaProgramInitiator.sol deleted file mode 100644 index 32733b7b7..000000000 --- a/contracts/contracts/coordination/BetaProgramInitiator.sol +++ /dev/null @@ -1,167 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "./FlatRateFeeModel.sol"; -import "./IEncryptionAuthorizer.sol"; -import "./Coordinator.sol"; - -contract BetaProgramInitiator { - using SafeERC20 for IERC20; - - event RequestRegistered( - address indexed sender, - uint256 indexed requestIndex, - address[] providers, - address authority, - uint32 duration, - IEncryptionAuthorizer accessController, - uint256 payment - ); - - event RequestCanceled(address indexed sender, uint256 indexed requestIndex); - - event RequestExecuted(uint256 indexed requestIndex, uint256 indexed ritualId); - - event FailedRequestRefunded(uint256 indexed requestIndex, uint256 refundAmount); - - struct InitiationRequest { - address[] providers; - address authority; - uint32 duration; - IEncryptionAuthorizer accessController; - address sender; - uint32 ritualId; - uint256 payment; - } - - uint32 public constant NO_RITUAL = type(uint32).max; - - Coordinator public immutable coordinator; - IERC20 public immutable currency; - address public immutable executor; // TODO transferable role? - FlatRateFeeModel public immutable feeModel; - - InitiationRequest[] public requests; - - constructor(Coordinator _coordinator, address _executor, FlatRateFeeModel _feeModel) { - require(_executor != address(0), "Invalid parameters"); - coordinator = _coordinator; - currency = _feeModel.currency(); - executor = _executor; - feeModel = _feeModel; - } - - function getRequestsLength() external view returns (uint256) { - return requests.length; - } - - function getProviders(uint256 requestIndex) external view returns (address[] memory) { - InitiationRequest storage request = requests[requestIndex]; - return request.providers; - } - - function registerInitiationRequest( - address[] calldata providers, - address authority, - uint32 duration, - IEncryptionAuthorizer accessController - ) external returns (uint256 requestIndex) { - uint256 ritualCost = feeModel.getRitualCost(providers.length, duration); - - requestIndex = requests.length; - InitiationRequest storage request = requests.push(); - request.providers = providers; - request.authority = authority; - request.duration = duration; - request.accessController = accessController; - request.sender = msg.sender; - request.ritualId = NO_RITUAL; - request.payment = ritualCost; - - emit RequestRegistered( - msg.sender, - requestIndex, - providers, - authority, - duration, - accessController, - ritualCost - ); - currency.safeTransferFrom(msg.sender, address(this), ritualCost); - - return requestIndex; - } - - function cancelInitiationRequest(uint256 requestIndex) external { - require(requestIndex < requests.length, "Non-existent request"); - InitiationRequest storage request = requests[requestIndex]; - address sender = request.sender; - require(msg.sender == sender || msg.sender == executor, "Not allowed to cancel"); - - uint256 ritualCost = request.payment; - require(request.ritualId == NO_RITUAL, "Request already executed"); - require(ritualCost != 0, "Request canceled"); - - // Erase payment and transfer refund to original sender - request.payment = 0; - emit RequestCanceled(msg.sender, requestIndex); - currency.safeTransfer(sender, ritualCost); - // TODO consider gas refund by setting zero values - } - - function executeInitiationRequest(uint256 requestIndex) external { - require(msg.sender == executor, "Only executor can call"); - - require(requestIndex < requests.length, "Non-existent request"); - InitiationRequest storage request = requests[requestIndex]; - require(request.ritualId == NO_RITUAL, "Request already executed"); - require(request.payment != 0, "Request canceled"); - - address[] memory providers = request.providers; - uint32 duration = request.duration; - uint256 ritualCost = feeModel.getRitualCost(providers.length, duration); - require(ritualCost == request.payment, "Ritual initiation cost has changed"); - currency.approve(address(feeModel), ritualCost); - - uint32 ritualId = coordinator.initiateRitual( - feeModel, - providers, - request.authority, - duration, - request.accessController - ); - request.ritualId = ritualId; - emit RequestExecuted(requestIndex, ritualId); - } - - function refundFailedRequest(uint256 requestIndex) external { - require(requestIndex < requests.length, "Non-existent request"); - InitiationRequest storage request = requests[requestIndex]; - uint32 ritualId = request.ritualId; - require(request.ritualId != NO_RITUAL, "Request is not executed"); - require(request.payment != 0, "Refund already processed"); - - Coordinator.RitualState state = coordinator.getRitualState(ritualId); - require( - state == Coordinator.RitualState.DKG_INVALID || - state == Coordinator.RitualState.DKG_TIMEOUT, - "Ritual is not failed" - ); - - // Process pending fees in Coordinator, if necessary - uint256 refundAmount = request.payment; - refundAmount -= feeModel.feeDeduction(request.payment, request.duration); - if (feeModel.pendingFees(ritualId) > 0) { - feeModel.processPendingFee(ritualId); - } - - // Erase payment and transfer refund to original sender - request.payment = 0; - currency.safeTransfer(request.sender, refundAmount); - emit FailedRequestRefunded(requestIndex, refundAmount); - // TODO consider gas refund by setting zero values - } -} diff --git a/contracts/contracts/coordination/Coordinator.sol b/contracts/contracts/coordination/Coordinator.sol index 9be31fb9d..32f2cbb55 100644 --- a/contracts/contracts/coordination/Coordinator.sol +++ b/contracts/contracts/coordination/Coordinator.sol @@ -88,7 +88,6 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable BLS12381.G2Point publicKey; } - bytes32 public constant INITIATOR_ROLE = keccak256("INITIATOR_ROLE"); bytes32 public constant TREASURY_ROLE = keccak256("TREASURY_ROLE"); ITACoChildApplication public immutable application; @@ -97,11 +96,11 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable Ritual[] public rituals; uint32 public timeout; uint16 public maxDkgSize; - bool public isInitiationPublic; + bool private stub1; // former isInitiationPublic - uint256 private stub1; // former totalPendingFees - mapping(uint256 => uint256) private stub2; // former pendingFees - address private stub3; // former feeModel + uint256 private stub2; // former totalPendingFees + mapping(uint256 => uint256) private stub3; // former pendingFees + address private stub4; // former feeModel IReimbursementPool internal reimbursementPool; mapping(address => ParticipantKey[]) internal participantKeysHistory; @@ -190,11 +189,6 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable } } - function makeInitiationPublic() external onlyRole(DEFAULT_ADMIN_ROLE) { - isInitiationPublic = true; - _setRoleAdmin(INITIATOR_ROLE, bytes32(0)); - } - function setProviderPublicKey(BLS12381.G2Point calldata publicKey) external { uint32 lastRitualId = uint32(rituals.length); address stakingProvider = application.operatorToStakingProvider(msg.sender); @@ -280,10 +274,6 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable ) external returns (uint32) { require(authority != address(0), "Invalid authority"); - require( - isInitiationPublic || hasRole(INITIATOR_ROLE, msg.sender), - "Sender can't initiate ritual" - ); require(feeModelsRegistry[feeModel], "Fee model must be approved"); uint16 length = uint16(providers.length); require(2 <= length && length <= maxDkgSize, "Invalid number of nodes"); diff --git a/contracts/test/BetaProgramInitiatorTestSet.sol b/contracts/test/BetaProgramInitiatorTestSet.sol deleted file mode 100644 index 91cdc17f8..000000000 --- a/contracts/test/BetaProgramInitiatorTestSet.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.0; - -import "../contracts/coordination/IEncryptionAuthorizer.sol"; -import "../contracts/coordination/Coordinator.sol"; -import "../contracts/coordination/IFeeModel.sol"; - -contract CoordinatorForBetaProgramInitiatorMock { - struct Ritual { - address initiator; - address[] providers; - address authority; - uint32 duration; - IEncryptionAuthorizer accessController; - Coordinator.RitualState state; - uint256 ritualCost; - IFeeModel feeModel; - } - - Ritual[] public rituals; - - function getRitualsLength() external view returns (uint256) { - return rituals.length; - } - - function getRitualState(uint32 _ritualId) external view returns (Coordinator.RitualState) { - if (_ritualId > rituals.length) { - return Coordinator.RitualState.NON_INITIATED; - } - return rituals[_ritualId].state; - } - - function getProviders(uint256 _ritualId) external view returns (address[] memory) { - Ritual storage ritual = rituals[_ritualId]; - return ritual.providers; - } - - function setRitualState(uint32 _ritualId, Coordinator.RitualState _state) external { - rituals[_ritualId].state = _state; - } - - function initiateRitual( - IFeeModel _feeModel, - address[] calldata _providers, - address _authority, - uint32 _duration, - IEncryptionAuthorizer _accessController - ) external returns (uint32 ritualId) { - Ritual storage ritual = rituals.push(); - ritual.initiator = msg.sender; - ritual.providers = _providers; - ritual.authority = _authority; - ritual.duration = _duration; - ritual.accessController = _accessController; - ritual.feeModel = _feeModel; - ritual.state = Coordinator.RitualState.DKG_AWAITING_TRANSCRIPTS; - - uint32 id = uint32(rituals.length - 1); - _feeModel.processRitualPayment(msg.sender, id, _providers.length, _duration); - - return id; - } - - function getInitiator(uint32 ritualId) external view returns (address) { - return rituals[ritualId].initiator; - } - - function getTimestamps( - uint32 ritualId - ) external view returns (uint32 initTimestamp, uint32 endTimestamp) { - initTimestamp = uint32(block.timestamp); - endTimestamp = uint32(block.timestamp + rituals[ritualId].duration); - } -} diff --git a/scripts/grant_initiator_role.py b/scripts/grant_initiator_role.py deleted file mode 100644 index 96b2beaad..000000000 --- a/scripts/grant_initiator_role.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/python3 - -import click -from ape import networks, project -from ape.cli import ConnectedProviderCommand, account_option, network_option - -from deployment.constants import SUPPORTED_TACO_DOMAINS -from deployment.params import Transactor -from deployment.registry import contracts_from_registry -from deployment.utils import check_plugins, registry_filepath_from_domain - - -@click.command(cls=ConnectedProviderCommand) -@network_option(required=True) -@account_option() -@click.option( - "--domain", - "-d", - help="TACo domain", - type=click.Choice(SUPPORTED_TACO_DOMAINS), - required=True, -) -@click.option( - "--grant-address", - "-g", - help="Address to grant initiator role", - type=str, - required=False, -) -def cli(network, account, domain, grant_address): - check_plugins() - transactor = Transactor(account) - registry_filepath = registry_filepath_from_domain(domain=domain) - deployments = contracts_from_registry( - filepath=registry_filepath, chain_id=networks.active_provider.chain_id - ) - coordinator = deployments[project.Coordinator.contract_type.name] - initiator_role_hash = coordinator.INITIATOR_ROLE() - transactor.transact( - coordinator.grantRole, - initiator_role_hash, - grant_address, # <- new initiator - ) - - -if __name__ == "__main__": - cli() diff --git a/tests/test_beta_program_initiator.py b/tests/test_beta_program_initiator.py deleted file mode 100644 index b7c4fa735..000000000 --- a/tests/test_beta_program_initiator.py +++ /dev/null @@ -1,465 +0,0 @@ -""" -This file is part of nucypher. - -nucypher is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -nucypher is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with nucypher. If not, see . -""" -from enum import IntEnum - -import ape -import pytest - -DAY_IN_SECONDS = 60 * 60 * 24 - -AUTHORITY_SLOT = 0 -DURATION_SLOT = 1 -ACCESS_CONTROLLER_SLOT = 2 -SENDER_SLOT = 3 -RITUAL_ID_SLOT = 4 -PAYMENT_SLOT = 5 -FEE_RATE = 42 - -RitualState = IntEnum( - "RitualState", - [ - "NON_INITIATED", - "DKG_AWAITING_TRANSCRIPTS", - "DKG_AWAITING_AGGREGATIONS", - "DKG_TIMEOUT", - "DKG_INVALID", - "ACTIVE", - "EXPIRED", - ], - start=0, -) - - -@pytest.fixture() -def executor(accounts): - return accounts[1] - - -@pytest.fixture() -def token(project, creator): - token = project.TestToken.deploy(0, sender=creator) - return token - - -@pytest.fixture() -def coordinator(project, creator): - contract = project.CoordinatorForBetaProgramInitiatorMock.deploy(sender=creator) - return contract - - -@pytest.fixture() -def fee_model(project, creator, coordinator, token): - contract = project.FlatRateFeeModel.deploy( - coordinator.address, token.address, FEE_RATE, sender=creator - ) - return contract - - -@pytest.fixture() -def beta_program_initiator(project, coordinator, executor, creator, fee_model): - contract = project.BetaProgramInitiator.deploy( - coordinator.address, executor, fee_model.address, sender=creator - ) - return contract - - -def test_register(accounts, beta_program_initiator, token, fee_model): - ( - initiator_1, - initiator_2, - authority, - node_1, - node_2, - access_controller, - *everyone_else, - ) = accounts[2:] - no_ritual = beta_program_initiator.NO_RITUAL() - - nodes = [node_1, node_2] - duration = DAY_IN_SECONDS - ritual_cost = fee_model.getRitualCost(len(nodes), duration) - - # Can't register request without token transfer approval - with ape.reverts(): - beta_program_initiator.registerInitiationRequest( - nodes, authority, duration, access_controller, sender=initiator_1 - ) - - # Register request - token.mint(initiator_1, 10 * ritual_cost, sender=initiator_1) - token.approve(beta_program_initiator.address, 10 * ritual_cost, sender=initiator_1) - tx = beta_program_initiator.registerInitiationRequest( - nodes, authority, duration, access_controller, sender=initiator_1 - ) - assert beta_program_initiator.getRequestsLength() == 1 - assert beta_program_initiator.getProviders(0) == nodes - request = beta_program_initiator.requests(0) - assert request[AUTHORITY_SLOT] == authority - assert request[DURATION_SLOT] == duration - assert request[ACCESS_CONTROLLER_SLOT] == access_controller - assert request[SENDER_SLOT] == initiator_1 - assert request[RITUAL_ID_SLOT] == no_ritual - assert request[PAYMENT_SLOT] == ritual_cost - assert token.balanceOf(beta_program_initiator) == ritual_cost - - events = beta_program_initiator.RequestRegistered.from_receipt(tx) - assert len(events) == 1 - event = events[0] - assert event.sender == initiator_1 - assert event.requestIndex == 0 - assert event.providers == [n.address for n in nodes] - assert event.authority == authority - assert event.duration == duration - assert event.accessController == access_controller - assert event.payment == ritual_cost - - # Register another request - nodes = [node_1] - duration = 3 * DAY_IN_SECONDS - ritual_cost_2 = fee_model.getRitualCost(len(nodes), duration) - - token.mint(initiator_2, ritual_cost_2, sender=initiator_2) - token.approve(beta_program_initiator.address, ritual_cost_2, sender=initiator_2) - tx = beta_program_initiator.registerInitiationRequest( - nodes, authority, duration, access_controller, sender=initiator_2 - ) - assert beta_program_initiator.getRequestsLength() == 2 - assert beta_program_initiator.getProviders(1) == nodes - request = beta_program_initiator.requests(1) - assert request[AUTHORITY_SLOT] == authority - assert request[DURATION_SLOT] == duration - assert request[ACCESS_CONTROLLER_SLOT] == access_controller - assert request[SENDER_SLOT] == initiator_2 - assert request[RITUAL_ID_SLOT] == no_ritual - assert request[PAYMENT_SLOT] == ritual_cost_2 - assert token.balanceOf(beta_program_initiator) == ritual_cost + ritual_cost_2 - - events = beta_program_initiator.RequestRegistered.from_receipt(tx) - assert len(events) == 1 - event = events[0] - assert event.sender == initiator_2 - assert event.requestIndex == 1 - assert event.providers == [n.address for n in nodes] - assert event.authority == authority - assert event.duration == duration - assert event.accessController == access_controller - assert event.payment == ritual_cost_2 - - -def test_cancel(accounts, beta_program_initiator, token, executor, fee_model): - ( - initiator_1, - initiator_2, - authority, - node_1, - node_2, - access_controller, - *everyone_else, - ) = accounts[2:] - - nodes = [node_1, node_2] - duration = DAY_IN_SECONDS - ritual_cost = fee_model.getRitualCost(len(nodes), duration) - - token.mint(initiator_1, 10 * ritual_cost, sender=initiator_1) - token.approve(beta_program_initiator.address, 10 * ritual_cost, sender=initiator_1) - token.mint(initiator_2, 10 * ritual_cost, sender=initiator_2) - token.approve(beta_program_initiator.address, 10 * ritual_cost, sender=initiator_2) - - # Can't cancel non-existent request - with ape.reverts("Non-existent request"): - beta_program_initiator.cancelInitiationRequest(0, sender=executor) - - # Register three requests - beta_program_initiator.registerInitiationRequest( - nodes, authority, duration, access_controller, sender=initiator_1 - ) - beta_program_initiator.registerInitiationRequest( - nodes, authority, duration, access_controller, sender=initiator_1 - ) - beta_program_initiator.registerInitiationRequest( - nodes, authority, duration, access_controller, sender=initiator_2 - ) - - # Only initiator or executor can cancel request - with ape.reverts("Not allowed to cancel"): - beta_program_initiator.cancelInitiationRequest(0, sender=initiator_2) - with ape.reverts("Not allowed to cancel"): - beta_program_initiator.cancelInitiationRequest(2, sender=initiator_1) - - # Initiator cancels request - balance_before = token.balanceOf(initiator_1) - tx = beta_program_initiator.cancelInitiationRequest(0, sender=initiator_1) - assert beta_program_initiator.requests(0)[PAYMENT_SLOT] == 0 - balance_after = token.balanceOf(initiator_1) - assert balance_after - balance_before == ritual_cost - - events = beta_program_initiator.RequestCanceled.from_receipt(tx) - assert events == [beta_program_initiator.RequestCanceled(initiator_1, 0)] - - # Executor cancels request - balance_before = token.balanceOf(initiator_2) - tx = beta_program_initiator.cancelInitiationRequest(2, sender=executor) - assert beta_program_initiator.requests(2)[PAYMENT_SLOT] == 0 - balance_after = token.balanceOf(initiator_2) - assert balance_after - balance_before == ritual_cost - - events = beta_program_initiator.RequestCanceled.from_receipt(tx) - assert events == [beta_program_initiator.RequestCanceled(executor, 2)] - - # Can't cancel twice - with ape.reverts("Request canceled"): - beta_program_initiator.cancelInitiationRequest(0, sender=executor) - with ape.reverts("Request canceled"): - beta_program_initiator.cancelInitiationRequest(2, sender=initiator_2) - - # Can't cancel an executed request - beta_program_initiator.executeInitiationRequest(1, sender=executor) - with ape.reverts("Request already executed"): - beta_program_initiator.cancelInitiationRequest(1, sender=executor) - - -def test_execute(accounts, beta_program_initiator, token, coordinator, executor, fee_model): - ( - initiator_1, - initiator_2, - authority_1, - authority_2, - node_1, - node_2, - access_controller_1, - access_controller_2, - *everyone_else, - ) = accounts[2:] - no_ritual = beta_program_initiator.NO_RITUAL() - - nodes_1 = [node_1, node_2] - duration_1 = DAY_IN_SECONDS - ritual_cost_1 = fee_model.getRitualCost(len(nodes_1), duration_1) - nodes_2 = [node_1] - duration_2 = 2 * duration_1 - ritual_cost_2 = fee_model.getRitualCost(len(nodes_2), duration_2) - - token.mint(initiator_1, 10 * ritual_cost_1, sender=initiator_1) - token.approve(beta_program_initiator.address, 10 * ritual_cost_1, sender=initiator_1) - token.mint(initiator_2, 10 * ritual_cost_2, sender=initiator_2) - token.approve(beta_program_initiator.address, 10 * ritual_cost_2, sender=initiator_2) - - # Can't execute non-existent request - with ape.reverts("Non-existent request"): - beta_program_initiator.executeInitiationRequest(0, sender=executor) - - # Register three requests - beta_program_initiator.registerInitiationRequest( - nodes_1, authority_1, duration_1, access_controller_1, sender=initiator_1 - ) - beta_program_initiator.registerInitiationRequest( - nodes_2, authority_2, duration_2, access_controller_2, sender=initiator_2 - ) - beta_program_initiator.registerInitiationRequest( - nodes_2, authority_1, duration_1, access_controller_2, sender=initiator_1 - ) - - # Only executor can execute request - with ape.reverts("Only executor can call"): - beta_program_initiator.executeInitiationRequest(0, sender=initiator_1) - - # Can't execute canceled request - beta_program_initiator.cancelInitiationRequest(2, sender=initiator_1) - with ape.reverts("Request canceled"): - beta_program_initiator.executeInitiationRequest(2, sender=executor) - - # Execute request - balance_before = token.balanceOf(beta_program_initiator.address) - tx = beta_program_initiator.executeInitiationRequest(1, sender=executor) - assert beta_program_initiator.requests(1)[RITUAL_ID_SLOT] == 0 - assert beta_program_initiator.requests(0)[RITUAL_ID_SLOT] == no_ritual - balance_after = token.balanceOf(beta_program_initiator.address) - assert balance_before - balance_after == ritual_cost_2 - assert token.balanceOf(fee_model.address) == ritual_cost_2 - - assert coordinator.getRitualsLength() == 1 - assert coordinator.getProviders(0) == nodes_2 - ritual = coordinator.rituals(0) - assert ritual[0] == beta_program_initiator.address - assert ritual[1] == authority_2 - assert ritual[2] == duration_2 - assert ritual[3] == access_controller_2 - assert ritual[4] == 1 - # assert ritual[5] == ritual_cost_2 - assert ritual[6] == fee_model.address - - events = beta_program_initiator.RequestExecuted.from_receipt(tx) - assert events == [beta_program_initiator.RequestExecuted(1, 0)] - - # Can't execute twice - with ape.reverts("Request already executed"): - beta_program_initiator.executeInitiationRequest(1, sender=executor) - - # Can't execute request if ritual cost changes - # fee_rate = fee_model.feeRatePerSecond() - # coordinator.setFeeRatePerSecond(2 * fee_rate, sender=executor) - # with ape.reverts("Ritual initiation cost has changed"): - # beta_program_initiator.executeInitiationRequest(0, sender=executor) - # coordinator.setFeeRatePerSecond(fee_rate // 2, sender=executor) - # with ape.reverts("Ritual initiation cost has changed"): - # beta_program_initiator.executeInitiationRequest(0, sender=executor) - - # Return fee rate back and execute request again - # coordinator.setFeeRatePerSecond(fee_rate, sender=executor) - balance_before = token.balanceOf(beta_program_initiator.address) - tx = beta_program_initiator.executeInitiationRequest(0, sender=executor) - assert beta_program_initiator.requests(0)[RITUAL_ID_SLOT] == 1 - assert beta_program_initiator.requests(1)[RITUAL_ID_SLOT] == 0 - balance_after = token.balanceOf(beta_program_initiator.address) - assert balance_before - balance_after == ritual_cost_1 - assert token.balanceOf(fee_model.address) == ritual_cost_2 + ritual_cost_1 - - assert coordinator.getRitualsLength() == 2 - assert coordinator.getProviders(1) == nodes_1 - ritual = coordinator.rituals(1) - assert ritual[0] == beta_program_initiator.address - assert ritual[1] == authority_1 - assert ritual[2] == duration_1 - assert ritual[3] == access_controller_1 - assert ritual[4] == 1 - # assert ritual[5] == ritual_cost_1 - assert ritual[6] == fee_model.address - - events = beta_program_initiator.RequestExecuted.from_receipt(tx) - assert events == [beta_program_initiator.RequestExecuted(0, 1)] - - -def test_refund(accounts, beta_program_initiator, token, coordinator, executor, fee_model): - ( - initiator_1, - initiator_2, - authority, - node_1, - node_2, - access_controller, - *everyone_else, - ) = accounts[2:] - - nodes = [node_1, node_2] - duration_1 = DAY_IN_SECONDS - ritual_cost_1 = fee_model.getRitualCost(len(nodes), duration_1) - duration_2 = 3 * duration_1 - ritual_cost_2 = fee_model.getRitualCost(len(nodes), duration_2) - - token.mint(initiator_1, 10 * ritual_cost_1, sender=initiator_1) - token.approve(beta_program_initiator.address, 10 * ritual_cost_1, sender=initiator_1) - token.mint(initiator_2, 10 * ritual_cost_2, sender=initiator_2) - token.approve(beta_program_initiator.address, 10 * ritual_cost_2, sender=initiator_2) - - # Can't refund non-existent request - with ape.reverts("Non-existent request"): - beta_program_initiator.refundFailedRequest(0, sender=executor) - - # Register three requests - beta_program_initiator.registerInitiationRequest( - nodes, authority, duration_1, access_controller, sender=initiator_1 - ) - beta_program_initiator.registerInitiationRequest( - nodes, authority, duration_2, access_controller, sender=initiator_2 - ) - beta_program_initiator.registerInitiationRequest( - nodes, authority, duration_2, access_controller, sender=initiator_1 - ) - - # Can't refund not executed request - with ape.reverts("Request is not executed"): - beta_program_initiator.refundFailedRequest(0, sender=executor) - beta_program_initiator.cancelInitiationRequest(2, sender=initiator_1) - with ape.reverts("Request is not executed"): - beta_program_initiator.refundFailedRequest(2, sender=initiator_2) - - # Can't refund not failed request - beta_program_initiator.executeInitiationRequest(1, sender=executor) - beta_program_initiator.executeInitiationRequest(0, sender=executor) - request_0_ritual_id = 1 - request_1_ritual_id = 0 - for state in [ - RitualState.NON_INITIATED, - RitualState.DKG_AWAITING_TRANSCRIPTS, - RitualState.DKG_AWAITING_AGGREGATIONS, - RitualState.ACTIVE, - RitualState.EXPIRED, - ]: - coordinator.setRitualState(request_0_ritual_id, state, sender=initiator_2) - with ape.reverts("Ritual is not failed"): - beta_program_initiator.refundFailedRequest(request_0_ritual_id, sender=initiator_2) - - # Refund failed request - coordinator.setRitualState(request_0_ritual_id, RitualState.DKG_TIMEOUT, sender=initiator_2) - - assert token.balanceOf(beta_program_initiator.address) == 0 - initiator_1_balance_before = token.balanceOf(initiator_1) - fee_model_balance_before = token.balanceOf(fee_model.address) - assert fee_model_balance_before == ritual_cost_1 + ritual_cost_2 - pending_fees_1_before = fee_model.pendingFees(request_0_ritual_id) - assert pending_fees_1_before == ritual_cost_1 - - tx = beta_program_initiator.refundFailedRequest(0, sender=initiator_2) - - fee_model_balance_after = token.balanceOf(fee_model.address) - fee_deduction_1 = fee_model.feeDeduction(pending_fees_1_before, duration_1) - pending_fees_1_after = fee_model.pendingFees(request_0_ritual_id) - assert fee_model_balance_after == ritual_cost_2 + fee_deduction_1 - assert pending_fees_1_after == 0 - assert token.balanceOf(beta_program_initiator.address) == 0 - - refund_1 = ritual_cost_1 - fee_deduction_1 - initiator_1_balance_after = token.balanceOf(initiator_1) - assert initiator_1_balance_after - initiator_1_balance_before == refund_1 - assert beta_program_initiator.requests(0)[RITUAL_ID_SLOT] == request_0_ritual_id - assert beta_program_initiator.requests(0)[PAYMENT_SLOT] == 0 - - events = beta_program_initiator.FailedRequestRefunded.from_receipt(tx) - assert events == [beta_program_initiator.FailedRequestRefunded(0, refund_1)] - - # Can't refund again - with ape.reverts("Refund already processed"): - beta_program_initiator.refundFailedRequest(0, sender=executor) - - # Refund failed request without pending fees - coordinator.setRitualState(request_1_ritual_id, RitualState.DKG_INVALID, sender=initiator_2) - - assert token.balanceOf(beta_program_initiator.address) == 0 - initiator_2_balance_before = token.balanceOf(initiator_2) - fee_model_balance_before = token.balanceOf(fee_model.address) - assert fee_model_balance_before == ritual_cost_2 + fee_deduction_1 - pending_fees_2_before = fee_model.pendingFees(request_1_ritual_id) - assert pending_fees_2_before == ritual_cost_2 - - fee_model.processPendingFee(request_1_ritual_id, sender=initiator_1) - assert fee_model.pendingFees(request_1_ritual_id) == 0 - - fee_deduction_2 = fee_model.feeDeduction(pending_fees_2_before, duration_2) - refund_2 = ritual_cost_2 - fee_deduction_2 - assert token.balanceOf(beta_program_initiator.address) == refund_2 - - tx = beta_program_initiator.refundFailedRequest(1, sender=initiator_2) - - assert token.balanceOf(beta_program_initiator.address) == 0 - initiator_2_balance_after = token.balanceOf(initiator_2) - assert initiator_2_balance_after - initiator_2_balance_before == refund_2 - assert beta_program_initiator.requests(1)[RITUAL_ID_SLOT] == request_1_ritual_id - assert beta_program_initiator.requests(1)[PAYMENT_SLOT] == 0 - - events = beta_program_initiator.FailedRequestRefunded.from_receipt(tx) - assert events == [beta_program_initiator.FailedRequestRefunded(1, refund_2)] diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index e8ce21ddf..58eb96ab3 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -101,8 +101,6 @@ def coordinator(project, deployer, application, initiator, oz_dependency): sender=deployer, ) proxy_contract = project.Coordinator.at(proxy.address) - - proxy_contract.grantRole(contract.INITIATOR_ROLE(), initiator, sender=admin) return proxy_contract @@ -133,12 +131,6 @@ def test_initial_parameters(coordinator): def test_invalid_initiate_ritual( project, coordinator, nodes, accounts, initiator, fee_model, global_allow_list ): - with ape.reverts("Sender can't initiate ritual"): - sender = accounts[3] - coordinator.initiateRitual( - fee_model.address, nodes, sender, DURATION, global_allow_list.address, sender=sender - ) - with ape.reverts("Invalid number of nodes"): coordinator.initiateRitual( fee_model.address, diff --git a/tests/test_global_allow_list.py b/tests/test_global_allow_list.py index 72a8a0a53..ad47e1b73 100644 --- a/tests/test_global_allow_list.py +++ b/tests/test_global_allow_list.py @@ -100,8 +100,6 @@ def coordinator(project, deployer, application, initiator, oz_dependency): sender=deployer, ) proxy_contract = project.Coordinator.at(proxy.address) - - proxy_contract.grantRole(contract.INITIATOR_ROLE(), initiator, sender=admin) return proxy_contract From 09562ccf114a3e222a3bf47e8710e8acecd08921 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Fri, 28 Jun 2024 07:44:03 -0400 Subject: [PATCH 049/105] Coordinator: return back isEncryptionAuthorized --- contracts/contracts/coordination/Coordinator.sol | 10 ++++++++++ tests/test_global_allow_list.py | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/contracts/contracts/coordination/Coordinator.sol b/contracts/contracts/coordination/Coordinator.sol index 32f2cbb55..0679e5228 100644 --- a/contracts/contracts/coordination/Coordinator.sol +++ b/contracts/contracts/coordination/Coordinator.sol @@ -539,6 +539,16 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable return found; } + function isEncryptionAuthorized( + uint32 ritualId, + bytes memory evidence, + bytes memory ciphertextHeader + ) external view returns (bool) { + Ritual storage ritual = rituals[ritualId]; + require(getRitualState(ritual) == RitualState.ACTIVE, "Ritual not active"); + return ritual.accessController.isAuthorized(ritualId, evidence, ciphertextHeader); + } + function processReimbursement(uint256 initialGasLeft) internal { if (address(reimbursementPool) != address(0)) { uint256 gasUsed = initialGasLeft - gasleft(); diff --git a/tests/test_global_allow_list.py b/tests/test_global_allow_list.py index ad47e1b73..c2d8efbca 100644 --- a/tests/test_global_allow_list.py +++ b/tests/test_global_allow_list.py @@ -166,6 +166,9 @@ def test_authorize_using_global_allow_list( with ape.reverts("Only active rituals can set authorizations"): global_allow_list.authorize(0, [deployer.address], sender=initiator) + with ape.reverts("Ritual not active"): + coordinator.isEncryptionAuthorized(0, bytes(signature), bytes(digest)) + # Finalize ritual transcript = os.urandom(transcript_size(len(nodes), len(nodes))) for node in nodes: @@ -184,6 +187,7 @@ def test_authorize_using_global_allow_list( # Authorized assert global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) + assert coordinator.isEncryptionAuthorized(0, bytes(signature), bytes(data)) events = global_allow_list.AddressAuthorizationSet.from_receipt(tx) assert events == [ global_allow_list.AddressAuthorizationSet( @@ -195,6 +199,7 @@ def test_authorize_using_global_allow_list( tx = global_allow_list.deauthorize(0, [deployer.address], sender=initiator) assert not global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) + assert not coordinator.isEncryptionAuthorized(0, bytes(signature), bytes(data)) events = global_allow_list.AddressAuthorizationSet.from_receipt(tx) assert events == [ global_allow_list.AddressAuthorizationSet( @@ -208,8 +213,10 @@ def test_authorize_using_global_allow_list( signed_digest = w3.eth.account.sign_message(signable_message, private_key=initiator.private_key) initiator_signature = signed_digest.signature assert global_allow_list.isAuthorized(0, bytes(initiator_signature), bytes(data)) + assert coordinator.isEncryptionAuthorized(0, bytes(initiator_signature), bytes(data)) assert global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) + assert coordinator.isEncryptionAuthorized(0, bytes(signature), bytes(data)) events = global_allow_list.AddressAuthorizationSet.from_receipt(tx) assert events == [ From 7048dd21989c19d3927618e0fecda76b264a0a0f Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Fri, 28 Jun 2024 07:59:33 -0400 Subject: [PATCH 050/105] GlobalAllowList: it's global allow list again --- contracts/contracts/coordination/Coordinator.sol | 4 ++++ contracts/contracts/coordination/GlobalAllowList.sol | 12 ++++-------- .../contracts/coordination/ManagedAllowList.sol | 5 ++--- tests/test_coordinator.py | 6 ++---- tests/test_global_allow_list.py | 6 ++---- 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/contracts/contracts/coordination/Coordinator.sol b/contracts/contracts/coordination/Coordinator.sol index 0679e5228..bfe049d81 100644 --- a/contracts/contracts/coordination/Coordinator.sol +++ b/contracts/contracts/coordination/Coordinator.sol @@ -143,6 +143,10 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable return rituals[ritualId].accessController; } + function getFeeModel(uint32 ritualId) external view returns (IFeeModel) { + return rituals[ritualId].feeModel; + } + function getRitualState(uint32 ritualId) external view returns (RitualState) { return getRitualState(rituals[ritualId]); } diff --git a/contracts/contracts/coordination/GlobalAllowList.sol b/contracts/contracts/coordination/GlobalAllowList.sol index c7658ac88..656073be2 100644 --- a/contracts/contracts/coordination/GlobalAllowList.sol +++ b/contracts/contracts/coordination/GlobalAllowList.sol @@ -17,7 +17,6 @@ contract GlobalAllowList is IEncryptionAuthorizer { using ECDSA for bytes32; Coordinator public immutable coordinator; - IFeeModel public immutable feeModel; mapping(bytes32 => bool) internal authorizations; @@ -41,16 +40,11 @@ contract GlobalAllowList is IEncryptionAuthorizer { * @notice Sets the coordinator contract * @dev The coordinator contract cannot be a zero address and must have a valid number of rituals * @param _coordinator The address of the coordinator contract - * @param _feeModel The address of the fee model contract */ - constructor(Coordinator _coordinator, IFeeModel _feeModel) { - require( - address(_coordinator) != address(0) && address(_feeModel) != address(0), - "Contracts cannot be zero addresses" - ); + constructor(Coordinator _coordinator) { + require(address(_coordinator) != address(0), "Contracts cannot be zero addresses"); require(_coordinator.numberOfRituals() >= 0, "Invalid coordinator"); coordinator = _coordinator; - feeModel = _feeModel; } /** @@ -88,6 +82,7 @@ contract GlobalAllowList is IEncryptionAuthorizer { // solhint-disable-next-line no-unused-vars bytes memory ciphertextHeader ) internal view virtual { + IFeeModel feeModel = coordinator.getFeeModel(ritualId); feeModel.beforeIsAuthorized(ritualId); } @@ -120,6 +115,7 @@ contract GlobalAllowList is IEncryptionAuthorizer { address[] calldata addresses, bool value ) internal virtual { + IFeeModel feeModel = coordinator.getFeeModel(ritualId); feeModel.beforeSetAuthorization(ritualId, addresses, value); } diff --git a/contracts/contracts/coordination/ManagedAllowList.sol b/contracts/contracts/coordination/ManagedAllowList.sol index 547c8d451..1d06f0777 100644 --- a/contracts/contracts/coordination/ManagedAllowList.sol +++ b/contracts/contracts/coordination/ManagedAllowList.sol @@ -39,9 +39,8 @@ contract ManagedAllowList is GlobalAllowList { */ constructor( Coordinator _coordinator, - IFeeModel _feeModel, - UpfrontSubscriptionWithEncryptorsCap _subscription - ) GlobalAllowList(_coordinator, _feeModel) { + UpfrontSubscriptionWithEncryptorsCap _subscription // TODO replace with IFeeModel subscription + ) GlobalAllowList(_coordinator) { require(address(_subscription) != address(0), "Subscription cannot be the zero address"); subscription = _subscription; } diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index 58eb96ab3..22b88b092 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -115,10 +115,8 @@ def fee_model(project, deployer, coordinator, erc20, treasury): @pytest.fixture() -def global_allow_list(project, deployer, coordinator, fee_model): - contract = project.GlobalAllowList.deploy( - coordinator.address, fee_model.address, sender=deployer - ) +def global_allow_list(project, deployer, coordinator): + contract = project.GlobalAllowList.deploy(coordinator.address, sender=deployer) return contract diff --git a/tests/test_global_allow_list.py b/tests/test_global_allow_list.py index c2d8efbca..1294fd3ae 100644 --- a/tests/test_global_allow_list.py +++ b/tests/test_global_allow_list.py @@ -114,10 +114,8 @@ def fee_model(project, deployer, coordinator, erc20, treasury): @pytest.fixture() -def global_allow_list(project, deployer, coordinator, fee_model): - contract = project.GlobalAllowList.deploy( - coordinator.address, fee_model.address, sender=deployer - ) +def global_allow_list(project, deployer, coordinator): + contract = project.GlobalAllowList.deploy(coordinator.address, sender=deployer) return contract From 082eb48ae5c2e25ac8fd929e169cfac5c5dc2db8 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Fri, 28 Jun 2024 12:31:28 -0400 Subject: [PATCH 051/105] Skip some tests in test_managed_allow_list --- contracts/test/BqETHSubscriptionTestSet.sol | 4 ++++ tests/test_bqeth_subscription.py | 8 +------- tests/test_managed_allow_list.py | 4 +++- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/test/BqETHSubscriptionTestSet.sol b/contracts/test/BqETHSubscriptionTestSet.sol index a31883a8d..0bbf188a4 100644 --- a/contracts/test/BqETHSubscriptionTestSet.sol +++ b/contracts/test/BqETHSubscriptionTestSet.sol @@ -59,6 +59,10 @@ contract CoordinatorForBqETHSubscriptionMock { return rituals[_ritualId].state; } + function getFeeModel(uint32) external view returns (IFeeModel) { + return feeModel; + } + function getTimestamps( uint32 _ritualId ) external view returns (uint32 initTimestamp, uint32 endTimestamp) { diff --git a/tests/test_bqeth_subscription.py b/tests/test_bqeth_subscription.py index d47ecbc6c..9aa26bf1d 100644 --- a/tests/test_bqeth_subscription.py +++ b/tests/test_bqeth_subscription.py @@ -105,9 +105,7 @@ def subscription(project, creator, coordinator, erc20, adopter, oz_dependency): @pytest.fixture() def global_allow_list(project, creator, coordinator, subscription, treasury): - contract = project.GlobalAllowList.deploy( - coordinator.address, subscription.address, sender=creator - ) + contract = project.GlobalAllowList.deploy(coordinator.address, sender=creator) subscription.initialize(treasury.address, contract.address, sender=treasury) return contract @@ -511,8 +509,6 @@ def test_before_set_authorization( ritual_id = 6 number_of_providers = 7 - assert subscription.address == global_allow_list.feeModel() - with ape.reverts("Only Access Controller can call this method"): subscription.beforeSetAuthorization(0, [creator], True, sender=adopter) @@ -576,8 +572,6 @@ def test_before_is_authorized( signed_digest = w3.eth.account.sign_message(signable_message, private_key=adopter.private_key) signature = signed_digest.signature - assert subscription.address == global_allow_list.feeModel() - with ape.reverts("Only Access Controller can call this method"): subscription.beforeIsAuthorized(0, sender=adopter) diff --git a/tests/test_managed_allow_list.py b/tests/test_managed_allow_list.py index 3f5905cd0..05000d854 100644 --- a/tests/test_managed_allow_list.py +++ b/tests/test_managed_allow_list.py @@ -63,7 +63,7 @@ def fee_model(project, deployer, coordinator, fee_token): @pytest.fixture() def brand_new_managed_allow_list(project, coordinator, subscription, fee_model, authority): return project.ManagedAllowList.deploy( - coordinator.address, fee_model.address, subscription.address, sender=authority + coordinator.address, subscription.address, sender=authority ) @@ -109,6 +109,7 @@ def test_remove_administrators(managed_allow_list, authority, admin): assert managed_allow_list.authActions(RITUAL_ID) == 2 +@pytest.mark.skip(reason="finish tests when managed allow list will use fee model") def test_authorize( managed_allow_list, subscription, fee_token, deployer, authority, admin, encryptor ): @@ -140,6 +141,7 @@ def test_authorize( assert managed_allow_list.isAddressAuthorized(RITUAL_ID, encryptor) +@pytest.mark.skip(reason="finish tests when managed allow list will use fee model") def test_deauthorize( managed_allow_list, subscription, fee_token, deployer, authority, admin, encryptor ): From 638bf51101a44a12a200a97dd1634dadbfc28365 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Tue, 2 Jul 2024 14:41:55 -0400 Subject: [PATCH 052/105] BqETHSubscription: immutable accesscontroller --- .../subscription/BqETHSubscription.sol | 20 +++++++++---------- tests/test_bqeth_subscription.py | 19 ++++++++++-------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/contracts/contracts/coordination/subscription/BqETHSubscription.sol b/contracts/contracts/coordination/subscription/BqETHSubscription.sol index 085782a6b..b89b2aedf 100644 --- a/contracts/contracts/coordination/subscription/BqETHSubscription.sol +++ b/contracts/contracts/coordination/subscription/BqETHSubscription.sol @@ -7,6 +7,7 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; import "./EncryptorSlotsSubscription.sol"; +import "../GlobalAllowList.sol"; /** * @title BqETH Subscription @@ -30,7 +31,7 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable uint256 public immutable encryptorFeeRate; uint256 public immutable maxNodes; - IEncryptionAuthorizer public accessController; + GlobalAllowList public immutable accessController; uint32 public activeRitualId; mapping(uint256 periodNumber => Billing billing) public billingInfo; @@ -75,6 +76,7 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable * @notice Sets the coordinator and fee token contracts * @dev The coordinator and fee token contracts cannot be zero addresses * @param _coordinator The address of the coordinator contract + * @param _accessController The address of the global allow list * @param _feeToken The address of the fee token contract * @param _adopter The address of the adopter * @param _baseFeeRate Fee rate per node per second @@ -86,6 +88,7 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable */ constructor( Coordinator _coordinator, + GlobalAllowList _accessController, IERC20 _feeToken, address _adopter, uint256 _baseFeeRate, @@ -104,11 +107,16 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable { require(address(_feeToken) != address(0), "Fee token cannot be the zero address"); require(_adopter != address(0), "Adopter cannot be the zero address"); + require( + address(_accessController) != address(0), + "Access controller cannot be the zero address" + ); feeToken = _feeToken; adopter = _adopter; baseFeeRate = _baseFeeRate; encryptorFeeRate = _encryptorFeeRate; maxNodes = _maxNodes; + accessController = _accessController; _disableInitializers(); } @@ -131,15 +139,7 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable /** * @notice Initialize function for using with OpenZeppelin proxy */ - function initialize( - address _treasury, - IEncryptionAuthorizer _accessController - ) external initializer { - require( - address(accessController) == address(0) && address(_accessController) != address(0), - "Access controller not already set and parameter cannot be the zero address" - ); - accessController = _accessController; + function initialize(address _treasury) external initializer { activeRitualId = INACTIVE_RITUAL_ID; __Ownable_init(_treasury); } diff --git a/tests/test_bqeth_subscription.py b/tests/test_bqeth_subscription.py index 9aa26bf1d..4252f2696 100644 --- a/tests/test_bqeth_subscription.py +++ b/tests/test_bqeth_subscription.py @@ -77,9 +77,18 @@ def coordinator(project, creator): @pytest.fixture() -def subscription(project, creator, coordinator, erc20, adopter, oz_dependency): +def global_allow_list(project, creator, coordinator): + contract = project.GlobalAllowList.deploy(coordinator.address, sender=creator) + return contract + + +@pytest.fixture() +def subscription( + project, creator, coordinator, global_allow_list, erc20, adopter, treasury, oz_dependency +): contract = project.BqETHSubscription.deploy( coordinator.address, + global_allow_list.address, erc20.address, adopter, BASE_FEE_RATE, @@ -100,16 +109,10 @@ def subscription(project, creator, coordinator, erc20, adopter, oz_dependency): ) proxy_contract = project.BqETHSubscription.at(proxy.address) coordinator.setFeeModel(proxy_contract.address, sender=creator) + proxy_contract.initialize(treasury.address, sender=treasury) return proxy_contract -@pytest.fixture() -def global_allow_list(project, creator, coordinator, subscription, treasury): - contract = project.GlobalAllowList.deploy(coordinator.address, sender=creator) - subscription.initialize(treasury.address, contract.address, sender=treasury) - return contract - - def test_pay_subscription( erc20, subscription, coordinator, global_allow_list, adopter, treasury, chain ): From 5a643a7297e68785c314d825920e0f1af69b61d8 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Tue, 2 Jul 2024 14:43:50 -0400 Subject: [PATCH 053/105] Coordinator: a deprecation notice comment for isEncryptionAuthorized --- contracts/contracts/coordination/Coordinator.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/contracts/coordination/Coordinator.sol b/contracts/contracts/coordination/Coordinator.sol index bfe049d81..125a559f5 100644 --- a/contracts/contracts/coordination/Coordinator.sol +++ b/contracts/contracts/coordination/Coordinator.sol @@ -543,6 +543,7 @@ contract Coordinator is Initializable, AccessControlDefaultAdminRulesUpgradeable return found; } + /// @dev Deprecated, see issue #195 function isEncryptionAuthorized( uint32 ritualId, bytes memory evidence, From ae20027d86412476b773f3968ed25010c2e61d31 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Wed, 3 Jul 2024 16:36:38 -0400 Subject: [PATCH 054/105] BqETHSubscription: base fee rate increase --- .../subscription/BqETHSubscription.sol | 31 +++++--- tests/test_bqeth_subscription.py | 72 +++++++++++++------ 2 files changed, 72 insertions(+), 31 deletions(-) diff --git a/contracts/contracts/coordination/subscription/BqETHSubscription.sol b/contracts/contracts/coordination/subscription/BqETHSubscription.sol index b89b2aedf..eefb559f1 100644 --- a/contracts/contracts/coordination/subscription/BqETHSubscription.sol +++ b/contracts/contracts/coordination/subscription/BqETHSubscription.sol @@ -21,17 +21,19 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable uint128 encryptorSlots; // pre-paid encryptor slots for the billing period } - IERC20 public immutable feeToken; - uint32 public constant INACTIVE_RITUAL_ID = type(uint32).max; + uint192 public constant INCREASE_BASE = 10000; + GlobalAllowList public immutable accessController; + IERC20 public immutable feeToken; address public immutable adopter; - uint256 public immutable baseFeeRate; + uint256 public immutable initialBaseFeeRate; uint256 public immutable encryptorFeeRate; uint256 public immutable maxNodes; - GlobalAllowList public immutable accessController; + uint256 public immutable baseFeeRateIncrease; + uint32 public activeRitualId; mapping(uint256 periodNumber => Billing billing) public billingInfo; @@ -79,7 +81,8 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable * @param _accessController The address of the global allow list * @param _feeToken The address of the fee token contract * @param _adopter The address of the adopter - * @param _baseFeeRate Fee rate per node per second + * @param _initialBaseFeeRate Fee rate per node per second + * @param _baseFeeRateIncrease Increase of base fee rate per each period (fraction of INCREASE_BASE) * @param _encryptorFeeRate Fee rate per encryptor per second * @param _maxNodes Maximum nodes in the package * @param _subscriptionPeriodDuration Maximum duration of subscription period @@ -91,7 +94,8 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable GlobalAllowList _accessController, IERC20 _feeToken, address _adopter, - uint256 _baseFeeRate, + uint256 _initialBaseFeeRate, + uint256 _baseFeeRateIncrease, uint256 _encryptorFeeRate, uint256 _maxNodes, uint32 _subscriptionPeriodDuration, @@ -113,7 +117,8 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable ); feeToken = _feeToken; adopter = _adopter; - baseFeeRate = _baseFeeRate; + initialBaseFeeRate = _initialBaseFeeRate; + baseFeeRateIncrease = _baseFeeRateIncrease; encryptorFeeRate = _encryptorFeeRate; maxNodes = _maxNodes; accessController = _accessController; @@ -145,7 +150,14 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable } function baseFees() public view returns (uint256) { - return baseFeeRate * subscriptionPeriodDuration * maxNodes; + uint256 currentPeriodNumber = getCurrentPeriodNumber(); + return baseFees(currentPeriodNumber); + } + + function baseFees(uint256 periodNumber) public view returns (uint256) { + uint256 baseFeeRate = initialBaseFeeRate * + (INCREASE_BASE + baseFeeRateIncrease * periodNumber); + return (baseFeeRate * subscriptionPeriodDuration * maxNodes) / INCREASE_BASE; } function encryptorFees(uint128 encryptorSlots, uint32 duration) public view returns (uint256) { @@ -175,7 +187,6 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable "Subscription is over" ); - uint256 fees = baseFees() + encryptorFees(encryptorSlots, subscriptionPeriodDuration); uint256 periodNumber = currentPeriodNumber; if (billingInfo[periodNumber].paid) { periodNumber++; @@ -184,6 +195,8 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable billing.paid = true; billing.encryptorSlots = encryptorSlots; + uint256 fees = baseFees(periodNumber) + + encryptorFees(encryptorSlots, subscriptionPeriodDuration); feeToken.safeTransferFrom(msg.sender, address(this), fees); emit SubscriptionPaid(msg.sender, fees, encryptorSlots, getEndOfSubscription()); } diff --git a/tests/test_bqeth_subscription.py b/tests/test_bqeth_subscription.py index 4252f2696..bd2aafe0d 100644 --- a/tests/test_bqeth_subscription.py +++ b/tests/test_bqeth_subscription.py @@ -23,6 +23,7 @@ from web3 import Web3 BASE_FEE_RATE = 42 +BASE_FEE_RATE_INCREASE = 10 # 10% MAX_NODES = 10 ENCRYPTORS_FEE_RATE = 77 @@ -35,7 +36,16 @@ YELLOW_PERIOD = ONE_DAY RED_PERIOD = 5 * ONE_DAY -BASE_FEE = BASE_FEE_RATE * PACKAGE_DURATION * MAX_NODES + +def base_fee(period_number): + return ( + BASE_FEE_RATE + * (100 + BASE_FEE_RATE_INCREASE * period_number) + * PACKAGE_DURATION + * MAX_NODES + // 100 + ) + RitualState = IntEnum( "RitualState", @@ -92,6 +102,7 @@ def subscription( erc20.address, adopter, BASE_FEE_RATE, + BASE_FEE_RATE_INCREASE * 100, ENCRYPTORS_FEE_RATE, MAX_NODES, PACKAGE_DURATION, @@ -116,11 +127,16 @@ def subscription( def test_pay_subscription( erc20, subscription, coordinator, global_allow_list, adopter, treasury, chain ): - erc20.approve(subscription.address, 10 * BASE_FEE, sender=adopter) + erc20.approve(subscription.address, ERC20_SUPPLY, sender=adopter) # First payment balance_before = erc20.balanceOf(adopter) - assert subscription.baseFees() == BASE_FEE + current_base_fee = base_fee(0) + assert subscription.baseFees() == current_base_fee + assert subscription.baseFees(0) == current_base_fee + assert subscription.baseFees(1) == base_fee(1) + assert subscription.baseFees(2) == base_fee(2) + assert subscription.baseFees(3) == base_fee(3) assert subscription.startOfSubscription() == 0 assert subscription.getEndOfSubscription() == 0 assert subscription.getCurrentPeriodNumber() == 0 @@ -134,14 +150,14 @@ def test_pay_subscription( assert subscription.billingInfo(0) == (True, 0) assert subscription.billingInfo(1) == (False, 0) balance_after = erc20.balanceOf(adopter) - assert balance_after + BASE_FEE == balance_before - assert erc20.balanceOf(subscription.address) == BASE_FEE + assert balance_after + current_base_fee == balance_before + assert erc20.balanceOf(subscription.address) == current_base_fee events = subscription.SubscriptionPaid.from_receipt(tx) assert events == [ subscription.SubscriptionPaid( subscriber=adopter, - amount=BASE_FEE, + amount=current_base_fee, encryptorSlots=0, endOfSubscription=end_subscription, ) @@ -151,12 +167,17 @@ def test_pay_subscription( encryptor_slots = 10 encryptor_fees = ENCRYPTORS_FEE_RATE * PACKAGE_DURATION * encryptor_slots balance_before = erc20.balanceOf(adopter) + subscription_balance_before = erc20.balanceOf(subscription.address) tx = subscription.payForSubscription(encryptor_slots, sender=adopter) end_subscription = 0 assert subscription.getEndOfSubscription() == end_subscription balance_after = erc20.balanceOf(adopter) - assert balance_after + BASE_FEE + encryptor_fees == balance_before - assert erc20.balanceOf(subscription.address) == 2 * BASE_FEE + encryptor_fees + current_base_fee = base_fee(1) + assert balance_after + current_base_fee + encryptor_fees == balance_before + assert ( + erc20.balanceOf(subscription.address) + == subscription_balance_before + current_base_fee + encryptor_fees + ) assert subscription.getCurrentPeriodNumber() == 0 assert subscription.billingInfo(0) == (True, 0) assert subscription.billingInfo(1) == (True, encryptor_slots) @@ -165,7 +186,7 @@ def test_pay_subscription( assert events == [ subscription.SubscriptionPaid( subscriber=adopter, - amount=BASE_FEE + encryptor_fees, + amount=current_base_fee + encryptor_fees, encryptorSlots=encryptor_slots, endOfSubscription=end_subscription, ) @@ -190,16 +211,22 @@ def test_pay_subscription( assert subscription.getEndOfSubscription() == end_subscription chain.pending_timestamp = timestamp + PACKAGE_DURATION + 1 + assert subscription.baseFees() == current_base_fee # Top up balance_before = erc20.balanceOf(adopter) + subscription_balance_before = erc20.balanceOf(subscription.address) tx = subscription.payForSubscription(encryptor_slots, sender=adopter) end_subscription = timestamp + 3 * PACKAGE_DURATION assert subscription.startOfSubscription() == timestamp assert subscription.getEndOfSubscription() == end_subscription balance_after = erc20.balanceOf(adopter) - assert balance_after + BASE_FEE + encryptor_fees == balance_before - assert erc20.balanceOf(subscription.address) == 3 * BASE_FEE + 2 * encryptor_fees + current_base_fee = base_fee(2) + assert balance_after + current_base_fee + encryptor_fees == balance_before + assert ( + erc20.balanceOf(subscription.address) + == subscription_balance_before + current_base_fee + encryptor_fees + ) assert subscription.getCurrentPeriodNumber() == 1 assert subscription.billingInfo(0) == (True, 0) assert subscription.billingInfo(1) == (True, encryptor_slots) @@ -209,7 +236,7 @@ def test_pay_subscription( assert events == [ subscription.SubscriptionPaid( subscriber=adopter, - amount=BASE_FEE + encryptor_fees, + amount=current_base_fee + encryptor_fees, encryptorSlots=encryptor_slots, endOfSubscription=end_subscription, ) @@ -231,7 +258,7 @@ def test_pay_encryptor_slots( == encryptor_slots * PACKAGE_DURATION * ENCRYPTORS_FEE_RATE ) - erc20.approve(subscription.address, 10 * BASE_FEE, sender=adopter) + erc20.approve(subscription.address, ERC20_SUPPLY, sender=adopter) with ape.reverts("Current billing period must be paid"): subscription.payForEncryptorSlots(encryptor_slots, sender=adopter) @@ -307,7 +334,7 @@ def test_pay_encryptor_slots( def test_withdraw(erc20, subscription, adopter, treasury, global_allow_list): - erc20.approve(subscription.address, 10 * BASE_FEE, sender=adopter) + erc20.approve(subscription.address, ERC20_SUPPLY, sender=adopter) with ape.reverts(): subscription.withdrawToTreasury(1, sender=adopter) @@ -317,15 +344,16 @@ def test_withdraw(erc20, subscription, adopter, treasury, global_allow_list): subscription.payForSubscription(0, sender=adopter) + current_base_fee = base_fee(0) with ape.reverts("Insufficient balance available"): - subscription.withdrawToTreasury(BASE_FEE + 1, sender=treasury) + subscription.withdrawToTreasury(current_base_fee + 1, sender=treasury) - tx = subscription.withdrawToTreasury(BASE_FEE, sender=treasury) - assert erc20.balanceOf(treasury) == BASE_FEE + tx = subscription.withdrawToTreasury(current_base_fee, sender=treasury) + assert erc20.balanceOf(treasury) == current_base_fee assert erc20.balanceOf(subscription.address) == 0 events = subscription.WithdrawalToTreasury.from_receipt(tx) - assert events == [subscription.WithdrawalToTreasury(treasury=treasury, amount=BASE_FEE)] + assert events == [subscription.WithdrawalToTreasury(treasury=treasury, amount=current_base_fee)] def test_process_ritual_payment( @@ -347,7 +375,7 @@ def test_process_ritual_payment( adopter, ritual_id, number_of_providers, DURATION, sender=treasury ) - erc20.approve(subscription.address, 10 * BASE_FEE, sender=adopter) + erc20.approve(subscription.address, ERC20_SUPPLY, sender=adopter) subscription.payForSubscription(0, sender=adopter) with ape.reverts("Ritual parameters exceed available in package"): @@ -446,7 +474,7 @@ def test_process_ritual_extending( treasury, ritual_id, number_of_providers, DURATION, sender=treasury ) - erc20.approve(subscription.address, 10 * BASE_FEE, sender=adopter) + erc20.approve(subscription.address, ERC20_SUPPLY, sender=adopter) subscription.payForSubscription(0, sender=adopter) coordinator.setRitual( ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury @@ -518,7 +546,7 @@ def test_before_set_authorization( with ape.reverts("Ritual must be active"): global_allow_list.authorize(0, [creator], sender=adopter) - erc20.approve(subscription.address, 10 * BASE_FEE, sender=adopter) + erc20.approve(subscription.address, ERC20_SUPPLY, sender=adopter) subscription.payForSubscription(0, sender=adopter) coordinator.setRitual( ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury @@ -581,7 +609,7 @@ def test_before_is_authorized( with ape.reverts("Ritual must be active"): global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) - erc20.approve(subscription.address, 10 * BASE_FEE, sender=adopter) + erc20.approve(subscription.address, ERC20_SUPPLY, sender=adopter) subscription.payForSubscription(1, sender=adopter) coordinator.setRitual( ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury From ae2de074213c0428c868cb1d5d1878e93180b40d Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Thu, 4 Jul 2024 09:19:30 -0400 Subject: [PATCH 055/105] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Núñez --- .../subscription/BqETHSubscription.sol | 14 +++++++++----- tests/test_bqeth_subscription.py | 4 ++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/contracts/contracts/coordination/subscription/BqETHSubscription.sol b/contracts/contracts/coordination/subscription/BqETHSubscription.sol index eefb559f1..777074884 100644 --- a/contracts/contracts/coordination/subscription/BqETHSubscription.sol +++ b/contracts/contracts/coordination/subscription/BqETHSubscription.sol @@ -22,18 +22,17 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable } uint32 public constant INACTIVE_RITUAL_ID = type(uint32).max; - uint192 public constant INCREASE_BASE = 10000; + uint256 public constant INCREASE_BASE = 10000; GlobalAllowList public immutable accessController; IERC20 public immutable feeToken; address public immutable adopter; uint256 public immutable initialBaseFeeRate; + uint256 public immutable baseFeeRateIncrease; uint256 public immutable encryptorFeeRate; uint256 public immutable maxNodes; - uint256 public immutable baseFeeRateIncrease; - uint32 public activeRitualId; mapping(uint256 periodNumber => Billing billing) public billingInfo; @@ -115,6 +114,10 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable address(_accessController) != address(0), "Access controller cannot be the zero address" ); + require( + _baseFeeRateIncrease < INCREASE_BASE, + "Base fee rate increase must be fraction of INCREASE_BASE" + ); feeToken = _feeToken; adopter = _adopter; initialBaseFeeRate = _initialBaseFeeRate; @@ -156,8 +159,9 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable function baseFees(uint256 periodNumber) public view returns (uint256) { uint256 baseFeeRate = initialBaseFeeRate * - (INCREASE_BASE + baseFeeRateIncrease * periodNumber); - return (baseFeeRate * subscriptionPeriodDuration * maxNodes) / INCREASE_BASE; + (INCREASE_BASE + baseFeeRateIncrease) ** periodNumber; + return + (baseFeeRate * subscriptionPeriodDuration * maxNodes) / (INCREASE_BASE ** periodNumber); } function encryptorFees(uint128 encryptorSlots, uint32 duration) public view returns (uint256) { diff --git a/tests/test_bqeth_subscription.py b/tests/test_bqeth_subscription.py index bd2aafe0d..44f0e2999 100644 --- a/tests/test_bqeth_subscription.py +++ b/tests/test_bqeth_subscription.py @@ -40,10 +40,10 @@ def base_fee(period_number): return ( BASE_FEE_RATE - * (100 + BASE_FEE_RATE_INCREASE * period_number) + * pow(100 + BASE_FEE_RATE_INCREASE, period_number) * PACKAGE_DURATION * MAX_NODES - // 100 + // pow(100, period_number) ) From 3c76b1ef648e3b748b5f8a1630b4df3d40257e52 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Mon, 8 Jul 2024 12:34:12 -0400 Subject: [PATCH 056/105] BqETHSubscription: note for base fee potential overflow --- .../contracts/coordination/subscription/BqETHSubscription.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/contracts/coordination/subscription/BqETHSubscription.sol b/contracts/contracts/coordination/subscription/BqETHSubscription.sol index 777074884..aa0bcd8f1 100644 --- a/contracts/contracts/coordination/subscription/BqETHSubscription.sol +++ b/contracts/contracts/coordination/subscription/BqETHSubscription.sol @@ -157,6 +157,7 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable return baseFees(currentPeriodNumber); } + /// @dev optential overflow after 15-16 periods function baseFees(uint256 periodNumber) public view returns (uint256) { uint256 baseFeeRate = initialBaseFeeRate * (INCREASE_BASE + baseFeeRateIncrease) ** periodNumber; From be506d206fc0bc20f31cc4c3152833b0151665e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Mon, 1 Jul 2024 17:16:50 +0200 Subject: [PATCH 057/105] First draft for BqETH deployment parameters --- .../lynx/beta_program_initiator.yml | 20 --------- deployment/constructor_params/lynx/bqeth.yml | 39 ++++++++++++++++++ .../mainnet/beta_program_initiator.yml | 20 --------- .../constructor_params/mainnet/bqeth.yml | 41 +++++++++++++++++++ 4 files changed, 80 insertions(+), 40 deletions(-) delete mode 100644 deployment/constructor_params/lynx/beta_program_initiator.yml create mode 100644 deployment/constructor_params/lynx/bqeth.yml delete mode 100644 deployment/constructor_params/mainnet/beta_program_initiator.yml create mode 100644 deployment/constructor_params/mainnet/bqeth.yml diff --git a/deployment/constructor_params/lynx/beta_program_initiator.yml b/deployment/constructor_params/lynx/beta_program_initiator.yml deleted file mode 100644 index 808102f91..000000000 --- a/deployment/constructor_params/lynx/beta_program_initiator.yml +++ /dev/null @@ -1,20 +0,0 @@ -deployment: - name: beta-program-initiator - chain_id: 80002 - -artifacts: - dir: ./deployment/artifacts/ - filename: beta_program_initiator_lynx.json - -constants: - # See deployment/artifacts/lynx.json - COORDINATOR_PROXY: "0xE9e94499bB0f67b9DBD75506ec1735486DE57770" - - # lynx deployer account - EXECUTOR: "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600" - -contracts: - - BetaProgramInitiator: - constructor: - _coordinator: $COORDINATOR_PROXY - _executor: $EXECUTOR diff --git a/deployment/constructor_params/lynx/bqeth.yml b/deployment/constructor_params/lynx/bqeth.yml new file mode 100644 index 000000000..4420abb37 --- /dev/null +++ b/deployment/constructor_params/lynx/bqeth.yml @@ -0,0 +1,39 @@ +deployment: + name: bqeth-lynx + chain_id: 80002 + +artifacts: + dir: ./deployment/artifacts/ + filename: bqeth-lynx.json + +constants: + # See deployment/artifacts/lynx.json + COORDINATOR_PROXY: "0xE9e94499bB0f67b9DBD75506ec1735486DE57770" + + # LynxRitualToken, see deployment/artifacts/lynx.json + LYNX_RITUAL_TOKEN: "0x064Be2a9740e565729BC0d47bC616c5bb8Cc87B9" + + # lynx deployer account + LYNX_DEPLOYER: "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600" + + BASE_FEE_RATE: 1 + ENCRYPTOR_FEE_RATE: 99 + MAX_NODES: 4 + + # TODO: Are these good period durations for testing in Lynx? + PERIOD: 172800 # 2 days + YELLOW_PERIOD: 86400 # 1 day + RED_PERIOD: 86400 # 1 day + +contracts: + - BqETHSubscription: + constructor: + _coordinator: $COORDINATOR_PROXY + _feeToken: $LYNX_RITUAL_TOKEN + _adopter: $LYNX_DEPLOYER + _baseFeeRate: $BASE_FEE_RATE + _encryptorFeeRate: $ENCRYPTOR_FEE_RATE + _maxNodes: $MAX_NODES + _subscriptionPeriodDuration: $PERIOD + _yellowPeriodDuration: $YELLOW_PERIOD + _redPeriodDuration: $RED_PERIOD diff --git a/deployment/constructor_params/mainnet/beta_program_initiator.yml b/deployment/constructor_params/mainnet/beta_program_initiator.yml deleted file mode 100644 index 502666ef8..000000000 --- a/deployment/constructor_params/mainnet/beta_program_initiator.yml +++ /dev/null @@ -1,20 +0,0 @@ -deployment: - name: beta-program-initiator - chain_id: 137 - -artifacts: - dir: ./deployment/artifacts/ - filename: beta_program_initiator.json - -constants: - # See deployment/artifacts/mainnet.json - COORDINATOR_PROXY: "0xE74259e3dafe30bAA8700238e324b47aC98FE755" - # See https://github.com/nucypher/tdec/issues/137#issuecomment-1881525878 - # and https://app.safe.global/home?safe=matic:0x861aa915C785dEe04684444560fC7A2AB43a1543 - EXECUTOR_MULTISIG: "0x861aa915C785dEe04684444560fC7A2AB43a1543" - -contracts: - - BetaProgramInitiator: - constructor: - _coordinator: $COORDINATOR_PROXY - _executor: $EXECUTOR_MULTISIG diff --git a/deployment/constructor_params/mainnet/bqeth.yml b/deployment/constructor_params/mainnet/bqeth.yml new file mode 100644 index 000000000..6d33472ec --- /dev/null +++ b/deployment/constructor_params/mainnet/bqeth.yml @@ -0,0 +1,41 @@ +deployment: + name: bqeth-mainnet + chain_id: 137 + +artifacts: + dir: ./deployment/artifacts/ + filename: bqeth-mainnet.json + +constants: + # See deployment/artifacts/mainnet.json + COORDINATOR_PROXY: "0xE74259e3dafe30bAA8700238e324b47aC98FE755" + + BQETH_ADOPTER: "TBD" + + # DAI Token on Polygon PoS - References: + # - https://polygonscan.com/token/0x8f3cf7ad23cd3cadbd9735aff958023239c6a063 + # - https://github.com/search?q=0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063&type=code + DAI_ON_POLYGON: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063" + + # See https://github.com/nucypher/tdec/issues/169 + BASE_FEE_RATE: 4050925925925 # $0.35 per day, in DAI units per second (in Python: 35*10**16 // 86400) + ENCRYPTOR_FEE_RATE: 63419583967 # $2 per year, in DAI units per second (in Python: 2 * 10**18 // 86400 // 365) + MAX_NODES: 30 + + # TODO: Are these good period durations for testing in Lynx? + PERIOD: 172800 # 2 days + YELLOW_PERIOD: 86400 # 1 day + RED_PERIOD: 86400 # 1 day + +contracts: + - BqETHSubscription: + constructor: + _coordinator: $COORDINATOR_PROXY + _feeToken: $DAI_ON_POLYGON + _adopter: $BQETH_ADOPTER + _baseFeeRate: $BASE_FEE_RATE + _encryptorFeeRate: $ENCRYPTOR_FEE_RATE + _maxNodes: $MAX_NODES + _subscriptionPeriodDuration: $PERIOD + _yellowPeriodDuration: $YELLOW_PERIOD + _redPeriodDuration: $RED_PERIOD From d5e9285ae910297ba42fa7831acf90826d25c842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Thu, 4 Jul 2024 19:03:44 +0200 Subject: [PATCH 058/105] Update BqETHSubscription deployment parameters * Automatic base fee increase parameters * Duration parameters * Deployment order changed after simplification, now GlobalAllowList is deployed first. * Added upgrade initialization * Added Threshold addresses --- deployment/constructor_params/lynx/bqeth.yml | 20 ++++++++--- .../constructor_params/mainnet/bqeth.yml | 36 +++++++++++++------ 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/deployment/constructor_params/lynx/bqeth.yml b/deployment/constructor_params/lynx/bqeth.yml index 4420abb37..6755d3705 100644 --- a/deployment/constructor_params/lynx/bqeth.yml +++ b/deployment/constructor_params/lynx/bqeth.yml @@ -16,24 +16,36 @@ constants: # lynx deployer account LYNX_DEPLOYER: "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600" - BASE_FEE_RATE: 1 - ENCRYPTOR_FEE_RATE: 99 MAX_NODES: 4 + # Let's use proposed values for mainnet. See https://github.com/nucypher/tdec/issues/169 + INITIAL_BASE_FEE_RATE: 4050925925925 # $0.35 per day, in DAI units per second (in Python: 35*10**16 // 86400) + ENCRYPTOR_FEE_RATE: 63419583967 # $2 per year, in DAI units per second (in Python: 2 * 10**18 // 86400 // 365) + BASE_FEE_RATE_INCREASE: 500 # 5%, expressed as parts on 10,000 + # TODO: Are these good period durations for testing in Lynx? PERIOD: 172800 # 2 days YELLOW_PERIOD: 86400 # 1 day RED_PERIOD: 86400 # 1 day contracts: + - GlobalAllowList: + constructor: + _coordinator: $COORDINATOR_PROXY - BqETHSubscription: + proxy: + constructor: + initialOwner: $LYNX_DEPLOYER # Upgrades owner + _data: $encode:initialize,$LYNX_DEPLOYER constructor: _coordinator: $COORDINATOR_PROXY + _accessController: $GlobalAllowList _feeToken: $LYNX_RITUAL_TOKEN _adopter: $LYNX_DEPLOYER - _baseFeeRate: $BASE_FEE_RATE + _initialBaseFeeRate: $INITIAL_BASE_FEE_RATE + _baseFeeRateIncrease: $BASE_FEE_RATE_INCREASE _encryptorFeeRate: $ENCRYPTOR_FEE_RATE _maxNodes: $MAX_NODES _subscriptionPeriodDuration: $PERIOD _yellowPeriodDuration: $YELLOW_PERIOD - _redPeriodDuration: $RED_PERIOD + _redPeriodDuration: $RED_PERIOD \ No newline at end of file diff --git a/deployment/constructor_params/mainnet/bqeth.yml b/deployment/constructor_params/mainnet/bqeth.yml index 6d33472ec..672317ab6 100644 --- a/deployment/constructor_params/mainnet/bqeth.yml +++ b/deployment/constructor_params/mainnet/bqeth.yml @@ -17,25 +17,41 @@ constants: # - https://github.com/search?q=0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063&type=code DAI_ON_POLYGON: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063" + # Threshold Network - References: + # - https://docs.threshold.network/resources/contract-addresses/mainnet/threshold-dao + # - https://github.com/keep-network/tbtc-v2/issues/594 + TREASURY_GUILD_ON_POLYGON: "0xc3Bf49eBA094AF346830dF4dbB42a07dE378EeB6" + THRESHOLD_COUNCIL_ON_POLYGON: "0x9F6e831c8F8939DC0C830C6e492e7cEf4f9C2F5f" + + # Subscription Parameters # See https://github.com/nucypher/tdec/issues/169 - BASE_FEE_RATE: 4050925925925 # $0.35 per day, in DAI units per second (in Python: 35*10**16 // 86400) - ENCRYPTOR_FEE_RATE: 63419583967 # $2 per year, in DAI units per second (in Python: 2 * 10**18 // 86400 // 365) - MAX_NODES: 30 - # TODO: Are these good period durations for testing in Lynx? - PERIOD: 172800 # 2 days - YELLOW_PERIOD: 86400 # 1 day - RED_PERIOD: 86400 # 1 day + # - Fee parameters: + INITIAL_BASE_FEE_RATE: 4050925925925 # $0.35 per day, in DAI units per second (in Python: 35*10**16 // 86400) + ENCRYPTOR_FEE_RATE: 63419583967 # $2 per year, in DAI units per second (in Python: 2 * 10**18 // 86400 // 365) + BASE_FEE_RATE_INCREASE: 500 # 5%, expressed as parts on 10,000 + + # - Duration parameters + SIX_MONTHS_IN_SECONDS: 15552000 # 180 days (~6 months) + THREE_MONTHS_IN_SECONDS: 7776000 # 90 day (~3 months) contracts: + - GlobalAllowList: + constructor: + _coordinator: $COORDINATOR_PROXY - BqETHSubscription: + proxy: + constructor: + initialOwner: $THRESHOLD_COUNCIL_ON_POLYGON # Upgrades owner + _data: $encode:initialize,$TREASURY_GUILD_ON_POLYGON constructor: _coordinator: $COORDINATOR_PROXY + _accessController: $GlobalAllowList _feeToken: $DAI_ON_POLYGON _adopter: $BQETH_ADOPTER _baseFeeRate: $BASE_FEE_RATE _encryptorFeeRate: $ENCRYPTOR_FEE_RATE _maxNodes: $MAX_NODES - _subscriptionPeriodDuration: $PERIOD - _yellowPeriodDuration: $YELLOW_PERIOD - _redPeriodDuration: $RED_PERIOD + _subscriptionPeriodDuration: $SIX_MONTHS_IN_SECONDS + _yellowPeriodDuration: $THREE_MONTHS_IN_SECONDS + _redPeriodDuration: $THREE_MONTHS_IN_SECONDS From c01cd1ac8da736324cf119b9b503ba90ae8aecb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Fri, 5 Jul 2024 10:24:20 +0200 Subject: [PATCH 059/105] Repurpose old BetaProgramInitiator deployer script for BqETHSubscription --- .../lynx/{deploy_beta_program.py => deploy_bqeth.py} | 11 ++++++++--- .../{deploy_beta_program.py => deploy_bqeth.py} | 9 +++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) rename scripts/lynx/{deploy_beta_program.py => deploy_bqeth.py} (75%) rename scripts/mainnet/{deploy_beta_program.py => deploy_bqeth.py} (77%) diff --git a/scripts/lynx/deploy_beta_program.py b/scripts/lynx/deploy_bqeth.py similarity index 75% rename from scripts/lynx/deploy_beta_program.py rename to scripts/lynx/deploy_bqeth.py index d7ffe75ea..6c472f229 100644 --- a/scripts/lynx/deploy_beta_program.py +++ b/scripts/lynx/deploy_bqeth.py @@ -9,14 +9,19 @@ from deployment.registry import merge_registries VERIFY = False -CONSTRUCTOR_PARAMS_FILEPATH = CONSTRUCTOR_PARAMS_DIR / "lynx" / "beta_program_initiator.yml" +CONSTRUCTOR_PARAMS_FILEPATH = CONSTRUCTOR_PARAMS_DIR / "lynx" / "bqeth.yml" LYNX_REGISTRY = ARTIFACTS_DIR / "lynx.json" def main(): deployer = Deployer.from_yaml(filepath=CONSTRUCTOR_PARAMS_FILEPATH, verify=VERIFY) - beta_program_initiator = deployer.deploy(project.BetaProgramInitiator) - deployments = [beta_program_initiator] + + global_allow_list = deployer.deploy(project.GlobalAllowList) + + bqeth_subscription = deployer.deploy(project.BqETHSubscription) + + deployments = [global_allow_list, bqeth_subscription] + deployer.finalize(deployments=deployments) merge_registries( registry_1_filepath=LYNX_REGISTRY, diff --git a/scripts/mainnet/deploy_beta_program.py b/scripts/mainnet/deploy_bqeth.py similarity index 77% rename from scripts/mainnet/deploy_beta_program.py rename to scripts/mainnet/deploy_bqeth.py index 27a009ac1..4a9a464d5 100644 --- a/scripts/mainnet/deploy_beta_program.py +++ b/scripts/mainnet/deploy_bqeth.py @@ -15,8 +15,13 @@ def main(): deployer = Deployer.from_yaml(filepath=CONSTRUCTOR_PARAMS_FILEPATH, verify=VERIFY) - beta_program_initiator = deployer.deploy(project.BetaProgramInitiator) - deployments = [beta_program_initiator] + + global_allow_list = deployer.deploy(project.GlobalAllowList) + + bqeth_subscription = deployer.deploy(project.BqETHSubscription) + + deployments = [global_allow_list, bqeth_subscription] + deployer.finalize(deployments=deployments) merge_registries( registry_1_filepath=MAINNET_REGISTRY, From 143b066c6cb2e100ae552b213ddd8ea410d1931a Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Fri, 5 Jul 2024 14:58:29 +0200 Subject: [PATCH 060/105] Add BqETHSubscription contract to registry ... and update GlobalAllowList --- deployment/artifacts/lynx.json | 954 ++++++++++++++++++++++++++++++++- 1 file changed, 933 insertions(+), 21 deletions(-) diff --git a/deployment/artifacts/lynx.json b/deployment/artifacts/lynx.json index 2950f536d..1a01057d6 100644 --- a/deployment/artifacts/lynx.json +++ b/deployment/artifacts/lynx.json @@ -2919,6 +2919,865 @@ "block_number": 5199800, "deployer": "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600" }, + "BqETHSubscription": { + "address": "0x3E851204c29742b713d5C243093E98691591e654", + "abi": [ + { + "type": "constructor", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "_coordinator", + "type": "address", + "components": null, + "internal_type": "contract Coordinator" + }, + { + "name": "_accessController", + "type": "address", + "components": null, + "internal_type": "contract GlobalAllowList" + }, + { + "name": "_feeToken", + "type": "address", + "components": null, + "internal_type": "contract IERC20" + }, + { + "name": "_adopter", + "type": "address", + "components": null, + "internal_type": "address" + }, + { + "name": "_initialBaseFeeRate", + "type": "uint256", + "components": null, + "internal_type": "uint256" + }, + { + "name": "_baseFeeRateIncrease", + "type": "uint256", + "components": null, + "internal_type": "uint256" + }, + { + "name": "_encryptorFeeRate", + "type": "uint256", + "components": null, + "internal_type": "uint256" + }, + { + "name": "_maxNodes", + "type": "uint256", + "components": null, + "internal_type": "uint256" + }, + { + "name": "_subscriptionPeriodDuration", + "type": "uint32", + "components": null, + "internal_type": "uint32" + }, + { + "name": "_yellowPeriodDuration", + "type": "uint32", + "components": null, + "internal_type": "uint32" + }, + { + "name": "_redPeriodDuration", + "type": "uint32", + "components": null, + "internal_type": "uint32" + } + ] + }, + { + "type": "error", + "name": "AddressEmptyCode", + "inputs": [ + { + "name": "target", + "type": "address", + "components": null, + "internal_type": "address" + } + ] + }, + { + "type": "error", + "name": "AddressInsufficientBalance", + "inputs": [ + { + "name": "account", + "type": "address", + "components": null, + "internal_type": "address" + } + ] + }, + { + "type": "error", + "name": "FailedInnerCall", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidInitialization", + "inputs": [] + }, + { + "type": "error", + "name": "NotInitializing", + "inputs": [] + }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { + "name": "owner", + "type": "address", + "components": null, + "internal_type": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "components": null, + "internal_type": "address" + } + ] + }, + { + "type": "error", + "name": "SafeERC20FailedOperation", + "inputs": [ + { + "name": "token", + "type": "address", + "components": null, + "internal_type": "address" + } + ] + }, + { + "type": "event", + "name": "EncryptorSlotsPaid", + "inputs": [ + { + "name": "sponsor", + "type": "address", + "components": null, + "internal_type": "address", + "indexed": true + }, + { + "name": "amount", + "type": "uint256", + "components": null, + "internal_type": "uint256", + "indexed": false + }, + { + "name": "encryptorSlots", + "type": "uint128", + "components": null, + "internal_type": "uint128", + "indexed": false + }, + { + "name": "endOfCurrentPeriod", + "type": "uint32", + "components": null, + "internal_type": "uint32", + "indexed": false + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint64", + "components": null, + "internal_type": "uint64", + "indexed": false + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "components": null, + "internal_type": "address", + "indexed": true + }, + { + "name": "newOwner", + "type": "address", + "components": null, + "internal_type": "address", + "indexed": true + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SubscriptionPaid", + "inputs": [ + { + "name": "subscriber", + "type": "address", + "components": null, + "internal_type": "address", + "indexed": true + }, + { + "name": "amount", + "type": "uint256", + "components": null, + "internal_type": "uint256", + "indexed": false + }, + { + "name": "encryptorSlots", + "type": "uint128", + "components": null, + "internal_type": "uint128", + "indexed": false + }, + { + "name": "endOfSubscription", + "type": "uint32", + "components": null, + "internal_type": "uint32", + "indexed": false + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "WithdrawalToTreasury", + "inputs": [ + { + "name": "treasury", + "type": "address", + "components": null, + "internal_type": "address", + "indexed": true + }, + { + "name": "amount", + "type": "uint256", + "components": null, + "internal_type": "uint256", + "indexed": false + } + ], + "anonymous": false + }, + { + "type": "function", + "name": "INACTIVE_RITUAL_ID", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint32", + "components": null, + "internal_type": "uint32" + } + ] + }, + { + "type": "function", + "name": "INCREASE_BASE", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "components": null, + "internal_type": "uint256" + } + ] + }, + { + "type": "function", + "name": "accessController", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "components": null, + "internal_type": "contract GlobalAllowList" + } + ] + }, + { + "type": "function", + "name": "activeRitualId", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint32", + "components": null, + "internal_type": "uint32" + } + ] + }, + { + "type": "function", + "name": "adopter", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "components": null, + "internal_type": "address" + } + ] + }, + { + "type": "function", + "name": "baseFeeRateIncrease", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "components": null, + "internal_type": "uint256" + } + ] + }, + { + "type": "function", + "name": "baseFees", + "stateMutability": "view", + "inputs": [ + { + "name": "periodNumber", + "type": "uint256", + "components": null, + "internal_type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "components": null, + "internal_type": "uint256" + } + ] + }, + { + "type": "function", + "name": "baseFees", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "components": null, + "internal_type": "uint256" + } + ] + }, + { + "type": "function", + "name": "beforeIsAuthorized", + "stateMutability": "view", + "inputs": [ + { + "name": "ritualId", + "type": "uint32", + "components": null, + "internal_type": "uint32" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "beforeSetAuthorization", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "ritualId", + "type": "uint32", + "components": null, + "internal_type": "uint32" + }, + { + "name": "addresses", + "type": "address[]", + "components": null, + "internal_type": "address[]" + }, + { + "name": "value", + "type": "bool", + "components": null, + "internal_type": "bool" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "billingInfo", + "stateMutability": "view", + "inputs": [ + { + "name": "periodNumber", + "type": "uint256", + "components": null, + "internal_type": "uint256" + } + ], + "outputs": [ + { + "name": "paid", + "type": "bool", + "components": null, + "internal_type": "bool" + }, + { + "name": "encryptorSlots", + "type": "uint128", + "components": null, + "internal_type": "uint128" + } + ] + }, + { + "type": "function", + "name": "coordinator", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "components": null, + "internal_type": "contract Coordinator" + } + ] + }, + { + "type": "function", + "name": "encryptorFeeRate", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "components": null, + "internal_type": "uint256" + } + ] + }, + { + "type": "function", + "name": "encryptorFees", + "stateMutability": "view", + "inputs": [ + { + "name": "encryptorSlots", + "type": "uint128", + "components": null, + "internal_type": "uint128" + }, + { + "name": "duration", + "type": "uint32", + "components": null, + "internal_type": "uint32" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "components": null, + "internal_type": "uint256" + } + ] + }, + { + "type": "function", + "name": "feeToken", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "components": null, + "internal_type": "contract IERC20" + } + ] + }, + { + "type": "function", + "name": "getCurrentPeriodNumber", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "components": null, + "internal_type": "uint256" + } + ] + }, + { + "type": "function", + "name": "getEndOfSubscription", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "endOfSubscription", + "type": "uint32", + "components": null, + "internal_type": "uint32" + } + ] + }, + { + "type": "function", + "name": "getPaidEncryptorSlots", + "stateMutability": "view", + "inputs": [ + { + "name": "periodNumber", + "type": "uint256", + "components": null, + "internal_type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "components": null, + "internal_type": "uint256" + } + ] + }, + { + "type": "function", + "name": "initialBaseFeeRate", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "components": null, + "internal_type": "uint256" + } + ] + }, + { + "type": "function", + "name": "initialize", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "_treasury", + "type": "address", + "components": null, + "internal_type": "address" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "isPeriodPaid", + "stateMutability": "view", + "inputs": [ + { + "name": "periodNumber", + "type": "uint256", + "components": null, + "internal_type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "components": null, + "internal_type": "bool" + } + ] + }, + { + "type": "function", + "name": "maxNodes", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "components": null, + "internal_type": "uint256" + } + ] + }, + { + "type": "function", + "name": "owner", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "components": null, + "internal_type": "address" + } + ] + }, + { + "type": "function", + "name": "payForEncryptorSlots", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "additionalEncryptorSlots", + "type": "uint128", + "components": null, + "internal_type": "uint128" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "payForSubscription", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "encryptorSlots", + "type": "uint128", + "components": null, + "internal_type": "uint128" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "processRitualExtending", + "stateMutability": "view", + "inputs": [ + { + "name": "", + "type": "address", + "components": null, + "internal_type": "address" + }, + { + "name": "ritualId", + "type": "uint32", + "components": null, + "internal_type": "uint32" + }, + { + "name": "", + "type": "uint256", + "components": null, + "internal_type": "uint256" + }, + { + "name": "", + "type": "uint32", + "components": null, + "internal_type": "uint32" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "processRitualPayment", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "initiator", + "type": "address", + "components": null, + "internal_type": "address" + }, + { + "name": "ritualId", + "type": "uint32", + "components": null, + "internal_type": "uint32" + }, + { + "name": "numberOfProviders", + "type": "uint256", + "components": null, + "internal_type": "uint256" + }, + { + "name": "duration", + "type": "uint32", + "components": null, + "internal_type": "uint32" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "redPeriodDuration", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint32", + "components": null, + "internal_type": "uint32" + } + ] + }, + { + "type": "function", + "name": "renounceOwnership", + "stateMutability": "nonpayable", + "inputs": [], + "outputs": [] + }, + { + "type": "function", + "name": "startOfSubscription", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint32", + "components": null, + "internal_type": "uint32" + } + ] + }, + { + "type": "function", + "name": "subscriptionPeriodDuration", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint32", + "components": null, + "internal_type": "uint32" + } + ] + }, + { + "type": "function", + "name": "transferOwnership", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "components": null, + "internal_type": "address" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "usedEncryptorSlots", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "components": null, + "internal_type": "uint256" + } + ] + }, + { + "type": "function", + "name": "withdrawToTreasury", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "amount", + "type": "uint256", + "components": null, + "internal_type": "uint256" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "yellowPeriodDuration", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint32", + "components": null, + "internal_type": "uint32" + } + ] + } + ], + "tx_hash": "0x709f8cb91d3489fed89b0006a162e226a12095e12c5b737d1ec639109cac4a26", + "block_number": 9101924, + "deployer": "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600" + }, "Coordinator": { "address": "0xE9e94499bB0f67b9DBD75506ec1735486DE57770", "abi": [ @@ -4700,7 +5559,7 @@ "deployer": "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600" }, "GlobalAllowList": { - "address": "0xfDBA7100B015586270B62bA116920b78F4ff6930", + "address": "0xd5a66BF5f63dccAFEC74AEe1ba755CD7e06F683a", "abi": [ { "type": "constructor", @@ -4709,7 +5568,8 @@ { "name": "_coordinator", "type": "address", - "internalType": "contract Coordinator" + "components": null, + "internal_type": "contract Coordinator" } ] }, @@ -4725,7 +5585,8 @@ { "name": "length", "type": "uint256", - "internalType": "uint256" + "components": null, + "internal_type": "uint256" } ] }, @@ -4736,7 +5597,8 @@ { "name": "s", "type": "bytes32", - "internalType": "bytes32" + "components": null, + "internal_type": "bytes32" } ] }, @@ -4747,24 +5609,62 @@ { "name": "ritualId", "type": "uint32", - "internalType": "uint32", + "components": null, + "internal_type": "uint32", "indexed": true }, { "name": "_address", "type": "address", - "internalType": "address", + "components": null, + "internal_type": "address", "indexed": true }, { "name": "isAuthorized", "type": "bool", - "internalType": "bool", + "components": null, + "internal_type": "bool", "indexed": false } ], "anonymous": false }, + { + "type": "function", + "name": "MAX_AUTH_ACTIONS", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint32", + "components": null, + "internal_type": "uint32" + } + ] + }, + { + "type": "function", + "name": "authActions", + "stateMutability": "view", + "inputs": [ + { + "name": "", + "type": "uint32", + "components": null, + "internal_type": "uint32" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "components": null, + "internal_type": "uint256" + } + ] + }, { "type": "function", "name": "authorize", @@ -4773,12 +5673,14 @@ { "name": "ritualId", "type": "uint32", - "internalType": "uint32" + "components": null, + "internal_type": "uint32" }, { "name": "addresses", "type": "address[]", - "internalType": "address[]" + "components": null, + "internal_type": "address[]" } ], "outputs": [] @@ -4792,7 +5694,8 @@ { "name": "", "type": "address", - "internalType": "contract Coordinator" + "components": null, + "internal_type": "contract Coordinator" } ] }, @@ -4804,12 +5707,14 @@ { "name": "ritualId", "type": "uint32", - "internalType": "uint32" + "components": null, + "internal_type": "uint32" }, { "name": "addresses", "type": "address[]", - "internalType": "address[]" + "components": null, + "internal_type": "address[]" } ], "outputs": [] @@ -4822,19 +5727,22 @@ { "name": "ritualId", "type": "uint32", - "internalType": "uint32" + "components": null, + "internal_type": "uint32" }, { "name": "encryptor", "type": "address", - "internalType": "address" + "components": null, + "internal_type": "address" } ], "outputs": [ { "name": "", "type": "bool", - "internalType": "bool" + "components": null, + "internal_type": "bool" } ] }, @@ -4846,30 +5754,34 @@ { "name": "ritualId", "type": "uint32", - "internalType": "uint32" + "components": null, + "internal_type": "uint32" }, { "name": "evidence", "type": "bytes", - "internalType": "bytes" + "components": null, + "internal_type": "bytes" }, { "name": "ciphertextHeader", "type": "bytes", - "internalType": "bytes" + "components": null, + "internal_type": "bytes" } ], "outputs": [ { "name": "", "type": "bool", - "internalType": "bool" + "components": null, + "internal_type": "bool" } ] } ], - "tx_hash": "0xe1f8d7d3afac631312665faab5e45a4a8bbbfa7180f22014b5ad2dc0497edf72", - "block_number": 5198678, + "tx_hash": "0x7753ec5286e2521a8430a9686052d7e065b3b561167123e6975f0ae1a2d312d0", + "block_number": 9101909, "deployer": "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600" }, "LynxRitualToken": { From 2af2aaaffd3c790006d947fa44776cea778e8f60 Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Fri, 5 Jul 2024 14:16:30 +0200 Subject: [PATCH 061/105] Updated ape modules --- deployment/params.py | 14 +++++++------- scripts/lynx/deploy_bqeth.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/deployment/params.py b/deployment/params.py index 97e3d613d..abcc99fd8 100644 --- a/deployment/params.py +++ b/deployment/params.py @@ -6,7 +6,7 @@ from ape import chain, networks from ape.api import AccountAPI, ReceiptAPI -from ape.cli import get_user_selected_account +from ape.cli.choices import select_account from ape.contracts.base import ContractContainer, ContractInstance, ContractTransactionHandler from ape.utils import EMPTY_BYTES32, ZERO_ADDRESS from eth_typing import ChecksumAddress @@ -482,7 +482,7 @@ class Transactor: def __init__(self, account: typing.Optional[AccountAPI] = None): if account is None: - self._account = get_user_selected_account() + self._account = select_account() else: self._account = account @@ -538,7 +538,7 @@ def __init__( constants = config.get("constants", {}) _Constants = namedtuple("_Constants", list(constants)) self.constants = _Constants(**constants) - + super().__init__(account) self._set_account(self._account) self.verify = verify @@ -588,7 +588,7 @@ def _deploy_contract( kwargs = self._get_kwargs() deployer_account = self.get_account() - return deployer_account.deploy(*deployment_params, + return deployer_account.deploy(*deployment_params, # FIXME: Manual gas fees - #199 # max_priority_fee="3 gwei", # max_fee="120 gwei", @@ -616,7 +616,7 @@ def _deploy_proxy( return contract_type_container.at(proxy_contract.address) def upgrade(self, container: ContractContainer, proxy_address, data=b'') -> ContractInstance: - + admin_slot = chain.provider.get_storage_at( address=proxy_address, slot=EIP1967_ADMIN_SLOT @@ -627,7 +627,7 @@ def upgrade(self, container: ContractContainer, proxy_address, data=b'') -> Cont f"Admin slot for contract at {proxy_address} is empty. " "Are you sure this is an EIP1967-compatible proxy?" ) - + admin_address = to_checksum_address(admin_slot[-20:]) proxy_admin = OZ_DEPENDENCY.ProxyAdmin.at(admin_address) # TODO: Check that owner of proxy admin is deployer @@ -636,7 +636,7 @@ def upgrade(self, container: ContractContainer, proxy_address, data=b'') -> Cont # TODO: initialize taco app implementation too self.transact(proxy_admin.upgradeAndCall, proxy_address, implementation.address, data) - + wrapped_instance = container.at(proxy_address) return wrapped_instance diff --git a/scripts/lynx/deploy_bqeth.py b/scripts/lynx/deploy_bqeth.py index 6c472f229..51ab62f26 100644 --- a/scripts/lynx/deploy_bqeth.py +++ b/scripts/lynx/deploy_bqeth.py @@ -21,7 +21,7 @@ def main(): bqeth_subscription = deployer.deploy(project.BqETHSubscription) deployments = [global_allow_list, bqeth_subscription] - + deployer.finalize(deployments=deployments) merge_registries( registry_1_filepath=LYNX_REGISTRY, From 6252ef1526694025e926ce1950762411903c29c6 Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Fri, 5 Jul 2024 16:45:17 +0200 Subject: [PATCH 062/105] Remove BetaProgramInitiator contract on Lynx registry and Tapir deployment. --- deployment/artifacts/lynx.json | 371 +-------------------------- scripts/tapir/deploy_beta_program.py | 25 -- 2 files changed, 1 insertion(+), 395 deletions(-) delete mode 100644 scripts/tapir/deploy_beta_program.py diff --git a/deployment/artifacts/lynx.json b/deployment/artifacts/lynx.json index 1a01057d6..44e9bc451 100644 --- a/deployment/artifacts/lynx.json +++ b/deployment/artifacts/lynx.json @@ -2550,375 +2550,6 @@ } }, "80002": { - "BetaProgramInitiator": { - "address": "0xf47dde316D994a050b8b4e5986e0790309979697", - "abi": [ - { - "type": "constructor", - "stateMutability": "nonpayable", - "inputs": [ - { - "name": "_coordinator", - "type": "address", - "internalType": "contract Coordinator" - }, - { - "name": "_executor", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "AddressEmptyCode", - "inputs": [ - { - "name": "target", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "AddressInsufficientBalance", - "inputs": [ - { - "name": "account", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "FailedInnerCall", - "inputs": [] - }, - { - "type": "error", - "name": "SafeERC20FailedOperation", - "inputs": [ - { - "name": "token", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "event", - "name": "FailedRequestRefunded", - "inputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256", - "indexed": true - }, - { - "name": "refundAmount", - "type": "uint256", - "internalType": "uint256", - "indexed": false - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "RequestCanceled", - "inputs": [ - { - "name": "sender", - "type": "address", - "internalType": "address", - "indexed": true - }, - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256", - "indexed": true - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "RequestExecuted", - "inputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256", - "indexed": true - }, - { - "name": "ritualId", - "type": "uint256", - "internalType": "uint256", - "indexed": true - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "RequestRegistered", - "inputs": [ - { - "name": "sender", - "type": "address", - "internalType": "address", - "indexed": true - }, - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256", - "indexed": true - }, - { - "name": "providers", - "type": "address[]", - "internalType": "address[]", - "indexed": false - }, - { - "name": "authority", - "type": "address", - "internalType": "address", - "indexed": false - }, - { - "name": "duration", - "type": "uint32", - "internalType": "uint32", - "indexed": false - }, - { - "name": "accessController", - "type": "address", - "internalType": "contract IEncryptionAuthorizer", - "indexed": false - }, - { - "name": "payment", - "type": "uint256", - "internalType": "uint256", - "indexed": false - } - ], - "anonymous": false - }, - { - "type": "function", - "name": "NO_RITUAL", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint32", - "internalType": "uint32" - } - ] - }, - { - "type": "function", - "name": "cancelInitiationRequest", - "stateMutability": "nonpayable", - "inputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [] - }, - { - "type": "function", - "name": "coordinator", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "contract Coordinator" - } - ] - }, - { - "type": "function", - "name": "currency", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "contract IERC20" - } - ] - }, - { - "type": "function", - "name": "executeInitiationRequest", - "stateMutability": "nonpayable", - "inputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [] - }, - { - "type": "function", - "name": "executor", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "function", - "name": "getProviders", - "stateMutability": "view", - "inputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "address[]", - "internalType": "address[]" - } - ] - }, - { - "type": "function", - "name": "getRequestsLength", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "function", - "name": "refundFailedRequest", - "stateMutability": "nonpayable", - "inputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [] - }, - { - "type": "function", - "name": "registerInitiationRequest", - "stateMutability": "nonpayable", - "inputs": [ - { - "name": "providers", - "type": "address[]", - "internalType": "address[]" - }, - { - "name": "authority", - "type": "address", - "internalType": "address" - }, - { - "name": "duration", - "type": "uint32", - "internalType": "uint32" - }, - { - "name": "accessController", - "type": "address", - "internalType": "contract IEncryptionAuthorizer" - } - ], - "outputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "function", - "name": "requests", - "stateMutability": "view", - "inputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "authority", - "type": "address", - "internalType": "address" - }, - { - "name": "duration", - "type": "uint32", - "internalType": "uint32" - }, - { - "name": "accessController", - "type": "address", - "internalType": "contract IEncryptionAuthorizer" - }, - { - "name": "sender", - "type": "address", - "internalType": "address" - }, - { - "name": "ritualId", - "type": "uint32", - "internalType": "uint32" - }, - { - "name": "payment", - "type": "uint256", - "internalType": "uint256" - } - ] - } - ], - "tx_hash": "0xbc06258dbf24b6da9d45619a3048e6894a2779a198d4b7e6cdc48470e8e93ee6", - "block_number": 5199800, - "deployer": "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600" - }, "BqETHSubscription": { "address": "0x3E851204c29742b713d5C243093E98691591e654", "abi": [ @@ -7362,4 +6993,4 @@ "deployer": "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600" } } -} \ No newline at end of file +} diff --git a/scripts/tapir/deploy_beta_program.py b/scripts/tapir/deploy_beta_program.py deleted file mode 100644 index d6c55a10e..000000000 --- a/scripts/tapir/deploy_beta_program.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/python3 - -from ape import project - -from deployment.constants import ( - CONSTRUCTOR_PARAMS_DIR, ARTIFACTS_DIR, -) -from deployment.params import Deployer -from deployment.registry import merge_registries - -VERIFY = False -CONSTRUCTOR_PARAMS_FILEPATH = CONSTRUCTOR_PARAMS_DIR / "tapir" / "beta_program_initiator.yml" -TAPIR_REGISTRY = ARTIFACTS_DIR / "tapir.json" - - -def main(): - deployer = Deployer.from_yaml(filepath=CONSTRUCTOR_PARAMS_FILEPATH, verify=VERIFY) - beta_program_initiator = deployer.deploy(project.BetaProgramInitiator) - deployments = [beta_program_initiator] - deployer.finalize(deployments=deployments) - merge_registries( - registry_1_filepath=TAPIR_REGISTRY, - registry_2_filepath=deployer.registry_filepath, - output_filepath=TAPIR_REGISTRY, - ) From 1cbe22753fac6789a64f92654f43d17223d7538a Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Fri, 5 Jul 2024 16:46:00 +0200 Subject: [PATCH 063/105] Fix compiler collision on OpenAccessAuthorizer --- contracts/contracts/testnet/OpenAccessAuthorizer.sol | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/contracts/contracts/testnet/OpenAccessAuthorizer.sol b/contracts/contracts/testnet/OpenAccessAuthorizer.sol index b94b71e07..717133f76 100644 --- a/contracts/contracts/testnet/OpenAccessAuthorizer.sol +++ b/contracts/contracts/testnet/OpenAccessAuthorizer.sol @@ -2,13 +2,7 @@ pragma solidity ^0.8.0; -interface IEncryptionAuthorizer { - function isAuthorized( - uint32 ritualId, - bytes memory evidence, - bytes memory ciphertextHeader - ) external view returns (bool); -} +import "../coordination/IEncryptionAuthorizer.sol"; contract OpenAccessAuthorizer is IEncryptionAuthorizer { function isAuthorized( From 71818547caa574743ccf0ca41eeacb1480cc7fd8 Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Fri, 5 Jul 2024 16:47:23 +0200 Subject: [PATCH 064/105] Update coordinator upgrade scripts --- .../constructor_params/lynx/upgrade-coordinator.yml | 3 --- .../mainnet/redeploy-coordinator.yml | 10 ---------- 2 files changed, 13 deletions(-) diff --git a/deployment/constructor_params/lynx/upgrade-coordinator.yml b/deployment/constructor_params/lynx/upgrade-coordinator.yml index 2ecf71d4a..3b20a7704 100644 --- a/deployment/constructor_params/lynx/upgrade-coordinator.yml +++ b/deployment/constructor_params/lynx/upgrade-coordinator.yml @@ -8,11 +8,8 @@ artifacts: constants: TACO_CHILD_APPLICATION: "0x42F30AEc1A36995eEFaf9536Eb62BD751F982D32" - LYNX_RITUAL_TOKEN: "0x064Be2a9740e565729BC0d47bC616c5bb8Cc87B9" contracts: - Coordinator: constructor: _application: $TACO_CHILD_APPLICATION - _currency: $LYNX_RITUAL_TOKEN - _feeRatePerSecond: 1 diff --git a/deployment/constructor_params/mainnet/redeploy-coordinator.yml b/deployment/constructor_params/mainnet/redeploy-coordinator.yml index fb18bd1aa..0b9d79887 100644 --- a/deployment/constructor_params/mainnet/redeploy-coordinator.yml +++ b/deployment/constructor_params/mainnet/redeploy-coordinator.yml @@ -10,17 +10,7 @@ constants: # See deployment/artifacts/mainnet.json TACO_CHILD_APPLICATION: "0xFa07aaB78062Fac4C36995bF28F6D677667973F5" - # DAI Token on Polygon PoS - References: - # - https://polygonscan.com/token/0x8f3cf7ad23cd3cadbd9735aff958023239c6a063 - # - https://github.com/search?q=0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063&type=code - DAI_ON_POLYGON: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063" - - # TACO specific constants: - PRIVATE_BETA_FEE_RATE: 4050925925925 # $0.35 per day, expressed in DAI units per seconds (in Python: 35*10**16 // 86400) - contracts: - Coordinator: constructor: _application: $TACO_CHILD_APPLICATION - _currency: $DAI_ON_POLYGON - _feeRatePerSecond: $PRIVATE_BETA_FEE_RATE From 5b57c0acf102d1f1f4b40e18e48813a4df486897 Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Fri, 5 Jul 2024 16:47:51 +0200 Subject: [PATCH 065/105] Add coordinator approve fee model script --- scripts/lynx/coordinator_approve_fee_model.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 scripts/lynx/coordinator_approve_fee_model.py diff --git a/scripts/lynx/coordinator_approve_fee_model.py b/scripts/lynx/coordinator_approve_fee_model.py new file mode 100644 index 000000000..7203ef767 --- /dev/null +++ b/scripts/lynx/coordinator_approve_fee_model.py @@ -0,0 +1,32 @@ +#!/usr/bin/python3 + +from ape import project, networks + +from deployment.constants import ARTIFACTS_DIR +from deployment.params import Transactor +from deployment.registry import contracts_from_registry + +LYNX_REGISTRY_FILEPATH = ARTIFACTS_DIR / "lynx.json" + +def main(): + """ + Coordinator approves the fee model for BqETHSubscription + + ape run lynx coordinator_approve_fee_model --network polygon:amoy:infura + """ + + transactor = Transactor() + deployments = contracts_from_registry( + filepath=LYNX_REGISTRY_FILEPATH, chain_id=networks.active_provider.chain_id + ) + coordinator = deployments[project.Coordinator.contract_type.name] + bqeth_subscription = deployments[project.BqETHSubscription.contract_type.name] + + # Grant TREASURY_ROLE + TREASURY_ROLE = coordinator.TREASURY_ROLE() + transactor.transact( + coordinator.grantRole, + TREASURY_ROLE, + transactor.get_account().address + ) + transactor.transact(coordinator.approveFeeModel, bqeth_subscription.address) From 36cfef2780b78adbe04789f9166cfda2f6c07b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Mon, 8 Jul 2024 12:00:34 +0200 Subject: [PATCH 066/105] BqETH will have 6 month periods, so period increase is 2.47% = sqrt(5%) --- deployment/constructor_params/mainnet/bqeth.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deployment/constructor_params/mainnet/bqeth.yml b/deployment/constructor_params/mainnet/bqeth.yml index 672317ab6..cc319096c 100644 --- a/deployment/constructor_params/mainnet/bqeth.yml +++ b/deployment/constructor_params/mainnet/bqeth.yml @@ -29,9 +29,8 @@ constants: # - Fee parameters: INITIAL_BASE_FEE_RATE: 4050925925925 # $0.35 per day, in DAI units per second (in Python: 35*10**16 // 86400) ENCRYPTOR_FEE_RATE: 63419583967 # $2 per year, in DAI units per second (in Python: 2 * 10**18 // 86400 // 365) - BASE_FEE_RATE_INCREASE: 500 # 5%, expressed as parts on 10,000 - - # - Duration parameters + BASE_FEE_RATE_INCREASE_PER_PERIOD: 247 # 5%/year ~ 2.47%/semester, expressed in basis points (0.01%) + # - Duration parameters --> 1 period = 6 months SIX_MONTHS_IN_SECONDS: 15552000 # 180 days (~6 months) THREE_MONTHS_IN_SECONDS: 7776000 # 90 day (~3 months) @@ -49,7 +48,8 @@ contracts: _accessController: $GlobalAllowList _feeToken: $DAI_ON_POLYGON _adopter: $BQETH_ADOPTER - _baseFeeRate: $BASE_FEE_RATE + _initialBaseFeeRate: $INITIAL_BASE_FEE_RATE + _baseFeeRateIncrease: $BASE_FEE_RATE_INCREASE_PER_PERIOD _encryptorFeeRate: $ENCRYPTOR_FEE_RATE _maxNodes: $MAX_NODES _subscriptionPeriodDuration: $SIX_MONTHS_IN_SECONDS From 1d29526207f91a72218e5a074b99dda72757c27c Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Fri, 5 Jul 2024 10:34:58 -0400 Subject: [PATCH 067/105] Adds free fee model --- .../contracts/coordination/FreeFeeModel.sol | 39 +++++++++++++++++++ .../contracts/coordination/IFeeModel.sol | 2 - 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 contracts/contracts/coordination/FreeFeeModel.sol diff --git a/contracts/contracts/coordination/FreeFeeModel.sol b/contracts/contracts/coordination/FreeFeeModel.sol new file mode 100644 index 000000000..726a6e1b1 --- /dev/null +++ b/contracts/contracts/coordination/FreeFeeModel.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/access/Ownable.sol"; + +/** + * @title FreeFeeModel + * @notice Free FeeModel + */ +contract FreeFeeModel is Ownable { + mapping(address initiator => bool approved) public initiatorWhiteList; + + constructor() Ownable(msg.sender) {} + + function approveInitiator(address initiator) external onlyOwner { + initiatorWhiteList[initiator] = true; + } + + function processRitualPayment(address initiator, uint32, uint256, uint32) external { + require(initiatorWhiteList[initiator], "Initiator not approved"); + } + + function processRitualExtending(address initiator, uint32, uint256, uint32) external { + require(initiatorWhiteList[initiator], "Initiator not approved"); + } + + function beforeSetAuthorization( + uint32 ritualId, + address[] calldata addresses, + bool value + ) external { + // solhint-disable-previous-line no-empty-blocks + } + + function beforeIsAuthorized(uint32 ritualId) external view { + // solhint-disable-previous-line no-empty-blocks + } +} diff --git a/contracts/contracts/coordination/IFeeModel.sol b/contracts/contracts/coordination/IFeeModel.sol index efb9701dd..6a0023048 100644 --- a/contracts/contracts/coordination/IFeeModel.sol +++ b/contracts/contracts/coordination/IFeeModel.sol @@ -2,8 +2,6 @@ pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - /** * @title IFeeModel * @notice IFeeModel From fd87a1136f995aa3433e7fd0129f582d107f7716 Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Fri, 5 Jul 2024 17:21:57 +0200 Subject: [PATCH 068/105] Fix deployer.finalize() function for ape 0.8.8 --- deployment/registry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deployment/registry.py b/deployment/registry.py index 74c7b8b41..fb94ef3a9 100644 --- a/deployment/registry.py +++ b/deployment/registry.py @@ -6,6 +6,7 @@ from typing import Dict, List, NamedTuple, Optional from ape.contracts import ContractInstance +from ape import chain from eth_typing import ChecksumAddress from eth_utils import to_checksum_address from web3.types import ABI @@ -59,7 +60,7 @@ def _get_entry( ) -> RegistryEntry: contract_abi = _get_abi(contract_instance) contract_name = _get_name(contract_instance=contract_instance, registry_names=registry_names) - receipt = contract_instance.receipt + receipt = chain.get_receipt(contract_instance.txn_hash) entry = RegistryEntry( name=contract_name, address=to_checksum_address(contract_instance.address), From 0bd3d0417511706ebb6dd27aa9e0fc1fce2f23c2 Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Fri, 5 Jul 2024 17:23:07 +0200 Subject: [PATCH 069/105] Add Free Fee Model deployment script --- .../lynx/free-fee-model.yml | 10 ++++++++ scripts/lynx/deploy_free_fee_model.py | 23 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 deployment/constructor_params/lynx/free-fee-model.yml create mode 100644 scripts/lynx/deploy_free_fee_model.py diff --git a/deployment/constructor_params/lynx/free-fee-model.yml b/deployment/constructor_params/lynx/free-fee-model.yml new file mode 100644 index 000000000..b57535ddb --- /dev/null +++ b/deployment/constructor_params/lynx/free-fee-model.yml @@ -0,0 +1,10 @@ +deployment: + name: lynx-free-fee-model + chain_id: 80002 + +artifacts: + dir: ./deployment/artifacts/ + filename: free-fee-model.json + +contracts: + - FreeFeeModel diff --git a/scripts/lynx/deploy_free_fee_model.py b/scripts/lynx/deploy_free_fee_model.py new file mode 100644 index 000000000..64d758523 --- /dev/null +++ b/scripts/lynx/deploy_free_fee_model.py @@ -0,0 +1,23 @@ +#!/usr/bin/python3 + +from ape import project + +from deployment.constants import ( + CONSTRUCTOR_PARAMS_DIR, ARTIFACTS_DIR, +) +from deployment.params import Deployer +from deployment.registry import merge_registries + +VERIFY = False +CONSTRUCTOR_PARAMS_FILEPATH = CONSTRUCTOR_PARAMS_DIR / "lynx" / "free-fee-model.yml" +LYNX_REGISTRY = ARTIFACTS_DIR / "lynx.json" + + +def main(): + deployer = Deployer.from_yaml(filepath=CONSTRUCTOR_PARAMS_FILEPATH, verify=VERIFY) + + free_fee_model = deployer.deploy(project.FreeFeeModel) + + deployments = [free_fee_model] + + deployer.finalize(deployments=deployments) From 98deceda37ee962adca267e594a033d1313a26e8 Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Fri, 5 Jul 2024 17:23:35 +0200 Subject: [PATCH 070/105] Add Free Fee Model contract to the Lynx registry --- deployment/artifacts/lynx.json | 234 ++++++++++++++++++++++++++++++++- 1 file changed, 233 insertions(+), 1 deletion(-) diff --git a/deployment/artifacts/lynx.json b/deployment/artifacts/lynx.json index 44e9bc451..b04fc81e3 100644 --- a/deployment/artifacts/lynx.json +++ b/deployment/artifacts/lynx.json @@ -5189,6 +5189,238 @@ "block_number": 5198668, "deployer": "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600" }, + "FreeFeeModel": { + "address": "0x14EB9BB700E45D2Ee9233056b8cc341276c688Ba", + "abi": [ + { + "type": "constructor", + "stateMutability": "nonpayable", + "inputs": [] + }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { + "name": "owner", + "type": "address", + "components": null, + "internal_type": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "components": null, + "internal_type": "address" + } + ] + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "components": null, + "internal_type": "address", + "indexed": true + }, + { + "name": "newOwner", + "type": "address", + "components": null, + "internal_type": "address", + "indexed": true + } + ], + "anonymous": false + }, + { + "type": "function", + "name": "approveInitiator", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "initiator", + "type": "address", + "components": null, + "internal_type": "address" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "beforeIsAuthorized", + "stateMutability": "view", + "inputs": [ + { + "name": "ritualId", + "type": "uint32", + "components": null, + "internal_type": "uint32" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "beforeSetAuthorization", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "ritualId", + "type": "uint32", + "components": null, + "internal_type": "uint32" + }, + { + "name": "addresses", + "type": "address[]", + "components": null, + "internal_type": "address[]" + }, + { + "name": "value", + "type": "bool", + "components": null, + "internal_type": "bool" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "initiatorWhiteList", + "stateMutability": "view", + "inputs": [ + { + "name": "initiator", + "type": "address", + "components": null, + "internal_type": "address" + } + ], + "outputs": [ + { + "name": "approved", + "type": "bool", + "components": null, + "internal_type": "bool" + } + ] + }, + { + "type": "function", + "name": "owner", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "components": null, + "internal_type": "address" + } + ] + }, + { + "type": "function", + "name": "processRitualExtending", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "initiator", + "type": "address", + "components": null, + "internal_type": "address" + }, + { + "name": "", + "type": "uint32", + "components": null, + "internal_type": "uint32" + }, + { + "name": "", + "type": "uint256", + "components": null, + "internal_type": "uint256" + }, + { + "name": "", + "type": "uint32", + "components": null, + "internal_type": "uint32" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "processRitualPayment", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "initiator", + "type": "address", + "components": null, + "internal_type": "address" + }, + { + "name": "", + "type": "uint32", + "components": null, + "internal_type": "uint32" + }, + { + "name": "", + "type": "uint256", + "components": null, + "internal_type": "uint256" + }, + { + "name": "", + "type": "uint32", + "components": null, + "internal_type": "uint32" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "renounceOwnership", + "stateMutability": "nonpayable", + "inputs": [], + "outputs": [] + }, + { + "type": "function", + "name": "transferOwnership", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "components": null, + "internal_type": "address" + } + ], + "outputs": [] + } + ], + "tx_hash": "0x9672d91bcbab5b746288e592e420c7cda089ef598023971d6525243d1504758b", + "block_number": 9106650, + "deployer": "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600" + }, "GlobalAllowList": { "address": "0xd5a66BF5f63dccAFEC74AEe1ba755CD7e06F683a", "abi": [ @@ -6993,4 +7225,4 @@ "deployer": "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600" } } -} +} \ No newline at end of file From 0d8b75ef8d2e5e41a41dd3318a675f0665504efb Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Fri, 5 Jul 2024 17:27:50 +0200 Subject: [PATCH 071/105] Add coordinator approve free fee model script --- .../coordinator_approve_free_fee_model.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 scripts/lynx/coordinator_approve_free_fee_model.py diff --git a/scripts/lynx/coordinator_approve_free_fee_model.py b/scripts/lynx/coordinator_approve_free_fee_model.py new file mode 100644 index 000000000..32db82302 --- /dev/null +++ b/scripts/lynx/coordinator_approve_free_fee_model.py @@ -0,0 +1,25 @@ +#!/usr/bin/python3 + +from ape import project, networks + +from deployment.constants import ARTIFACTS_DIR +from deployment.params import Transactor +from deployment.registry import contracts_from_registry + +LYNX_REGISTRY_FILEPATH = ARTIFACTS_DIR / "lynx.json" + +def main(): + """ + Coordinator approves the fee model for Free Fee Model + + ape run lynx coordinator_approve_free_fee_model --network polygon:amoy:infura + """ + + transactor = Transactor() + deployments = contracts_from_registry( + filepath=LYNX_REGISTRY_FILEPATH, chain_id=networks.active_provider.chain_id + ) + coordinator = deployments[project.Coordinator.contract_type.name] + free_fee_model = deployments[project.FreeFeeModel.contract_type.name] + + transactor.transact(coordinator.approveFeeModel, free_fee_model.address) From df95f180ef1f6b03887529f3e30c301ecd8e7cd7 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Mon, 8 Jul 2024 15:06:27 -0400 Subject: [PATCH 072/105] Stop using deprecated method in favour of updated method. This will prevent the weird ABI values from being used ("internal_type" and "components": null) in the resulting contract registry during deployment finalization. --- deployment/registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/registry.py b/deployment/registry.py index fb94ef3a9..8e1483efa 100644 --- a/deployment/registry.py +++ b/deployment/registry.py @@ -36,7 +36,7 @@ def _get_abi(contract_instance: ContractInstance) -> ABI: """Returns the ABI of a contract instance.""" contract_abi = list() for entry in contract_instance.contract_type.abi: - contract_abi.append(entry.dict()) + contract_abi.append(entry.model_dump()) return contract_abi From ab98e51b7d9692fb2976ea7091bce6b7b26c9c65 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Mon, 8 Jul 2024 15:13:13 -0400 Subject: [PATCH 073/105] Fix contract registry to remove weird ABI values due to deprecated function being used to obtain ABI from ape contract type. --- deployment/artifacts/lynx.json | 362 +++++++++++---------------------- 1 file changed, 121 insertions(+), 241 deletions(-) diff --git a/deployment/artifacts/lynx.json b/deployment/artifacts/lynx.json index b04fc81e3..3e6bb0344 100644 --- a/deployment/artifacts/lynx.json +++ b/deployment/artifacts/lynx.json @@ -2560,68 +2560,57 @@ { "name": "_coordinator", "type": "address", - "components": null, - "internal_type": "contract Coordinator" + "internalType": "contract Coordinator" }, { "name": "_accessController", "type": "address", - "components": null, - "internal_type": "contract GlobalAllowList" + "internalType": "contract GlobalAllowList" }, { "name": "_feeToken", "type": "address", - "components": null, - "internal_type": "contract IERC20" + "internalType": "contract IERC20" }, { "name": "_adopter", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" }, { "name": "_initialBaseFeeRate", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" }, { "name": "_baseFeeRateIncrease", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" }, { "name": "_encryptorFeeRate", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" }, { "name": "_maxNodes", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" }, { "name": "_subscriptionPeriodDuration", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" }, { "name": "_yellowPeriodDuration", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" }, { "name": "_redPeriodDuration", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" } ] }, @@ -2632,8 +2621,7 @@ { "name": "target", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" } ] }, @@ -2644,8 +2632,7 @@ { "name": "account", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" } ] }, @@ -2671,8 +2658,7 @@ { "name": "owner", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" } ] }, @@ -2683,8 +2669,7 @@ { "name": "account", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" } ] }, @@ -2695,8 +2680,7 @@ { "name": "token", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" } ] }, @@ -2707,29 +2691,25 @@ { "name": "sponsor", "type": "address", - "components": null, - "internal_type": "address", + "internalType": "address", "indexed": true }, { "name": "amount", "type": "uint256", - "components": null, - "internal_type": "uint256", + "internalType": "uint256", "indexed": false }, { "name": "encryptorSlots", "type": "uint128", - "components": null, - "internal_type": "uint128", + "internalType": "uint128", "indexed": false }, { "name": "endOfCurrentPeriod", "type": "uint32", - "components": null, - "internal_type": "uint32", + "internalType": "uint32", "indexed": false } ], @@ -2742,8 +2722,7 @@ { "name": "version", "type": "uint64", - "components": null, - "internal_type": "uint64", + "internalType": "uint64", "indexed": false } ], @@ -2756,15 +2735,13 @@ { "name": "previousOwner", "type": "address", - "components": null, - "internal_type": "address", + "internalType": "address", "indexed": true }, { "name": "newOwner", "type": "address", - "components": null, - "internal_type": "address", + "internalType": "address", "indexed": true } ], @@ -2777,29 +2754,25 @@ { "name": "subscriber", "type": "address", - "components": null, - "internal_type": "address", + "internalType": "address", "indexed": true }, { "name": "amount", "type": "uint256", - "components": null, - "internal_type": "uint256", + "internalType": "uint256", "indexed": false }, { "name": "encryptorSlots", "type": "uint128", - "components": null, - "internal_type": "uint128", + "internalType": "uint128", "indexed": false }, { "name": "endOfSubscription", "type": "uint32", - "components": null, - "internal_type": "uint32", + "internalType": "uint32", "indexed": false } ], @@ -2812,15 +2785,13 @@ { "name": "treasury", "type": "address", - "components": null, - "internal_type": "address", + "internalType": "address", "indexed": true }, { "name": "amount", "type": "uint256", - "components": null, - "internal_type": "uint256", + "internalType": "uint256", "indexed": false } ], @@ -2835,8 +2806,7 @@ { "name": "", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" } ] }, @@ -2849,8 +2819,7 @@ { "name": "", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" } ] }, @@ -2863,8 +2832,7 @@ { "name": "", "type": "address", - "components": null, - "internal_type": "contract GlobalAllowList" + "internalType": "contract GlobalAllowList" } ] }, @@ -2877,8 +2845,7 @@ { "name": "", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" } ] }, @@ -2891,8 +2858,7 @@ { "name": "", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" } ] }, @@ -2905,8 +2871,7 @@ { "name": "", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" } ] }, @@ -2918,16 +2883,14 @@ { "name": "periodNumber", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" } ], "outputs": [ { "name": "", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" } ] }, @@ -2940,8 +2903,7 @@ { "name": "", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" } ] }, @@ -2953,8 +2915,7 @@ { "name": "ritualId", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" } ], "outputs": [] @@ -2967,20 +2928,17 @@ { "name": "ritualId", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" }, { "name": "addresses", "type": "address[]", - "components": null, - "internal_type": "address[]" + "internalType": "address[]" }, { "name": "value", "type": "bool", - "components": null, - "internal_type": "bool" + "internalType": "bool" } ], "outputs": [] @@ -2993,22 +2951,19 @@ { "name": "periodNumber", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" } ], "outputs": [ { "name": "paid", "type": "bool", - "components": null, - "internal_type": "bool" + "internalType": "bool" }, { "name": "encryptorSlots", "type": "uint128", - "components": null, - "internal_type": "uint128" + "internalType": "uint128" } ] }, @@ -3021,8 +2976,7 @@ { "name": "", "type": "address", - "components": null, - "internal_type": "contract Coordinator" + "internalType": "contract Coordinator" } ] }, @@ -3035,8 +2989,7 @@ { "name": "", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" } ] }, @@ -3048,22 +3001,19 @@ { "name": "encryptorSlots", "type": "uint128", - "components": null, - "internal_type": "uint128" + "internalType": "uint128" }, { "name": "duration", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" } ], "outputs": [ { "name": "", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" } ] }, @@ -3076,8 +3026,7 @@ { "name": "", "type": "address", - "components": null, - "internal_type": "contract IERC20" + "internalType": "contract IERC20" } ] }, @@ -3090,8 +3039,7 @@ { "name": "", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" } ] }, @@ -3104,8 +3052,7 @@ { "name": "endOfSubscription", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" } ] }, @@ -3117,16 +3064,14 @@ { "name": "periodNumber", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" } ], "outputs": [ { "name": "", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" } ] }, @@ -3139,8 +3084,7 @@ { "name": "", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" } ] }, @@ -3152,8 +3096,7 @@ { "name": "_treasury", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" } ], "outputs": [] @@ -3166,16 +3109,14 @@ { "name": "periodNumber", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" } ], "outputs": [ { "name": "", "type": "bool", - "components": null, - "internal_type": "bool" + "internalType": "bool" } ] }, @@ -3188,8 +3129,7 @@ { "name": "", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" } ] }, @@ -3202,8 +3142,7 @@ { "name": "", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" } ] }, @@ -3215,8 +3154,7 @@ { "name": "additionalEncryptorSlots", "type": "uint128", - "components": null, - "internal_type": "uint128" + "internalType": "uint128" } ], "outputs": [] @@ -3229,8 +3167,7 @@ { "name": "encryptorSlots", "type": "uint128", - "components": null, - "internal_type": "uint128" + "internalType": "uint128" } ], "outputs": [] @@ -3243,26 +3180,22 @@ { "name": "", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" }, { "name": "ritualId", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" }, { "name": "", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" }, { "name": "", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" } ], "outputs": [] @@ -3275,26 +3208,22 @@ { "name": "initiator", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" }, { "name": "ritualId", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" }, { "name": "numberOfProviders", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" }, { "name": "duration", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" } ], "outputs": [] @@ -3308,8 +3237,7 @@ { "name": "", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" } ] }, @@ -3329,8 +3257,7 @@ { "name": "", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" } ] }, @@ -3343,8 +3270,7 @@ { "name": "", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" } ] }, @@ -3356,8 +3282,7 @@ { "name": "newOwner", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" } ], "outputs": [] @@ -3371,8 +3296,7 @@ { "name": "", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" } ] }, @@ -3384,8 +3308,7 @@ { "name": "amount", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" } ], "outputs": [] @@ -3399,8 +3322,7 @@ { "name": "", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" } ] } @@ -5204,8 +5126,7 @@ { "name": "owner", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" } ] }, @@ -5216,8 +5137,7 @@ { "name": "account", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" } ] }, @@ -5228,15 +5148,13 @@ { "name": "previousOwner", "type": "address", - "components": null, - "internal_type": "address", + "internalType": "address", "indexed": true }, { "name": "newOwner", "type": "address", - "components": null, - "internal_type": "address", + "internalType": "address", "indexed": true } ], @@ -5250,8 +5168,7 @@ { "name": "initiator", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" } ], "outputs": [] @@ -5264,8 +5181,7 @@ { "name": "ritualId", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" } ], "outputs": [] @@ -5278,20 +5194,17 @@ { "name": "ritualId", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" }, { "name": "addresses", "type": "address[]", - "components": null, - "internal_type": "address[]" + "internalType": "address[]" }, { "name": "value", "type": "bool", - "components": null, - "internal_type": "bool" + "internalType": "bool" } ], "outputs": [] @@ -5304,16 +5217,14 @@ { "name": "initiator", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" } ], "outputs": [ { "name": "approved", "type": "bool", - "components": null, - "internal_type": "bool" + "internalType": "bool" } ] }, @@ -5326,8 +5237,7 @@ { "name": "", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" } ] }, @@ -5339,26 +5249,22 @@ { "name": "initiator", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" }, { "name": "", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" }, { "name": "", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" }, { "name": "", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" } ], "outputs": [] @@ -5371,26 +5277,22 @@ { "name": "initiator", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" }, { "name": "", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" }, { "name": "", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" }, { "name": "", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" } ], "outputs": [] @@ -5410,8 +5312,7 @@ { "name": "newOwner", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" } ], "outputs": [] @@ -5431,8 +5332,7 @@ { "name": "_coordinator", "type": "address", - "components": null, - "internal_type": "contract Coordinator" + "internalType": "contract Coordinator" } ] }, @@ -5448,8 +5348,7 @@ { "name": "length", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" } ] }, @@ -5460,8 +5359,7 @@ { "name": "s", "type": "bytes32", - "components": null, - "internal_type": "bytes32" + "internalType": "bytes32" } ] }, @@ -5472,22 +5370,19 @@ { "name": "ritualId", "type": "uint32", - "components": null, - "internal_type": "uint32", + "internalType": "uint32", "indexed": true }, { "name": "_address", "type": "address", - "components": null, - "internal_type": "address", + "internalType": "address", "indexed": true }, { "name": "isAuthorized", "type": "bool", - "components": null, - "internal_type": "bool", + "internalType": "bool", "indexed": false } ], @@ -5502,8 +5397,7 @@ { "name": "", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" } ] }, @@ -5515,16 +5409,14 @@ { "name": "", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" } ], "outputs": [ { "name": "", "type": "uint256", - "components": null, - "internal_type": "uint256" + "internalType": "uint256" } ] }, @@ -5536,14 +5428,12 @@ { "name": "ritualId", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" }, { "name": "addresses", "type": "address[]", - "components": null, - "internal_type": "address[]" + "internalType": "address[]" } ], "outputs": [] @@ -5557,8 +5447,7 @@ { "name": "", "type": "address", - "components": null, - "internal_type": "contract Coordinator" + "internalType": "contract Coordinator" } ] }, @@ -5570,14 +5459,12 @@ { "name": "ritualId", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" }, { "name": "addresses", "type": "address[]", - "components": null, - "internal_type": "address[]" + "internalType": "address[]" } ], "outputs": [] @@ -5590,22 +5477,19 @@ { "name": "ritualId", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" }, { "name": "encryptor", "type": "address", - "components": null, - "internal_type": "address" + "internalType": "address" } ], "outputs": [ { "name": "", "type": "bool", - "components": null, - "internal_type": "bool" + "internalType": "bool" } ] }, @@ -5617,28 +5501,24 @@ { "name": "ritualId", "type": "uint32", - "components": null, - "internal_type": "uint32" + "internalType": "uint32" }, { "name": "evidence", "type": "bytes", - "components": null, - "internal_type": "bytes" + "internalType": "bytes" }, { "name": "ciphertextHeader", "type": "bytes", - "components": null, - "internal_type": "bytes" + "internalType": "bytes" } ], "outputs": [ { "name": "", "type": "bool", - "components": null, - "internal_type": "bool" + "internalType": "bool" } ] } @@ -7225,4 +7105,4 @@ "deployer": "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600" } } -} \ No newline at end of file +} From 85d92f5d86291b5ea41eef2ba881a4cd317a06ae Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Tue, 9 Jul 2024 11:13:17 +0200 Subject: [PATCH 074/105] Update Coordinator ABI --- deployment/artifacts/lynx.json | 347 ++++++++++++++------------------- 1 file changed, 148 insertions(+), 199 deletions(-) diff --git a/deployment/artifacts/lynx.json b/deployment/artifacts/lynx.json index 3e6bb0344..7d1f65d6c 100644 --- a/deployment/artifacts/lynx.json +++ b/deployment/artifacts/lynx.json @@ -3342,16 +3342,6 @@ "name": "_application", "type": "address", "internalType": "contract ITACoChildApplication" - }, - { - "name": "_currency", - "type": "address", - "internalType": "contract IERC20" - }, - { - "name": "_feeRatePerSecond", - "type": "uint256", - "internalType": "uint256" } ] }, @@ -3403,33 +3393,6 @@ } ] }, - { - "type": "error", - "name": "AddressEmptyCode", - "inputs": [ - { - "name": "target", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "AddressInsufficientBalance", - "inputs": [ - { - "name": "account", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "FailedInnerCall", - "inputs": [] - }, { "type": "error", "name": "InvalidInitialization", @@ -3456,17 +3419,6 @@ } ] }, - { - "type": "error", - "name": "SafeERC20FailedOperation", - "inputs": [ - { - "name": "token", - "type": "address", - "internalType": "address" - } - ] - }, { "type": "event", "name": "AggregationPosted", @@ -3561,6 +3513,19 @@ ], "anonymous": false }, + { + "type": "event", + "name": "FeeModelApproved", + "inputs": [ + { + "name": "feeModel", + "type": "address", + "internalType": "contract IFeeModel", + "indexed": false + } + ], + "anonymous": false + }, { "type": "event", "name": "Initialized", @@ -3673,6 +3638,25 @@ ], "anonymous": false }, + { + "type": "event", + "name": "RitualExtended", + "inputs": [ + { + "name": "ritualId", + "type": "uint32", + "internalType": "uint32", + "indexed": true + }, + { + "name": "endTimestamp", + "type": "uint32", + "internalType": "uint32", + "indexed": false + } + ], + "anonymous": false + }, { "type": "event", "name": "RoleAdminChanged", @@ -3843,19 +3827,6 @@ } ] }, - { - "type": "function", - "name": "INITIATOR_ROLE", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ] - }, { "type": "function", "name": "TREASURY_ROLE", @@ -3889,6 +3860,19 @@ } ] }, + { + "type": "function", + "name": "approveFeeModel", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "feeModel", + "type": "address", + "internalType": "contract IFeeModel" + } + ], + "outputs": [] + }, { "type": "function", "name": "beginDefaultAdminTransfer", @@ -3943,33 +3927,33 @@ }, { "type": "function", - "name": "currency", + "name": "defaultAdmin", "stateMutability": "view", "inputs": [], "outputs": [ { "name": "", "type": "address", - "internalType": "contract IERC20" + "internalType": "address" } ] }, { "type": "function", - "name": "defaultAdmin", + "name": "defaultAdminDelay", "stateMutability": "view", "inputs": [], "outputs": [ { "name": "", - "type": "address", - "internalType": "address" + "type": "uint48", + "internalType": "uint48" } ] }, { "type": "function", - "name": "defaultAdminDelay", + "name": "defaultAdminDelayIncreaseWait", "stateMutability": "view", "inputs": [], "outputs": [ @@ -3982,57 +3966,101 @@ }, { "type": "function", - "name": "defaultAdminDelayIncreaseWait", + "name": "extendRitual", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "ritualId", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "duration", + "type": "uint32", + "internalType": "uint32" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "feeModelsRegistry", "stateMutability": "view", - "inputs": [], + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IFeeModel" + } + ], "outputs": [ { "name": "", - "type": "uint48", - "internalType": "uint48" + "type": "bool", + "internalType": "bool" } ] }, { "type": "function", - "name": "feeDeduction", - "stateMutability": "pure", + "name": "getAccessController", + "stateMutability": "view", "inputs": [ { - "name": "", - "type": "uint256", - "internalType": "uint256" - }, + "name": "ritualId", + "type": "uint32", + "internalType": "uint32" + } + ], + "outputs": [ { "name": "", - "type": "uint256", - "internalType": "uint256" + "type": "address", + "internalType": "contract IEncryptionAuthorizer" + } + ] + }, + { + "type": "function", + "name": "getAuthority", + "stateMutability": "view", + "inputs": [ + { + "name": "ritualId", + "type": "uint32", + "internalType": "uint32" } ], "outputs": [ { "name": "", - "type": "uint256", - "internalType": "uint256" + "type": "address", + "internalType": "address" } ] }, { "type": "function", - "name": "feeRatePerSecond", + "name": "getFeeModel", "stateMutability": "view", - "inputs": [], + "inputs": [ + { + "name": "ritualId", + "type": "uint32", + "internalType": "uint32" + } + ], "outputs": [ { "name": "", - "type": "uint256", - "internalType": "uint256" + "type": "address", + "internalType": "contract IFeeModel" } ] }, { "type": "function", - "name": "getAuthority", + "name": "getInitiator", "stateMutability": "view", "inputs": [ { @@ -4365,30 +4393,6 @@ } ] }, - { - "type": "function", - "name": "getRitualInitiationCost", - "stateMutability": "view", - "inputs": [ - { - "name": "providers", - "type": "address[]", - "internalType": "address[]" - }, - { - "name": "duration", - "type": "uint32", - "internalType": "uint32" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ] - }, { "type": "function", "name": "getRitualState", @@ -4446,6 +4450,30 @@ } ] }, + { + "type": "function", + "name": "getTimestamps", + "stateMutability": "view", + "inputs": [ + { + "name": "ritualId", + "type": "uint32", + "internalType": "uint32" + } + ], + "outputs": [ + { + "name": "initTimestamp", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "endTimestamp", + "type": "uint32", + "internalType": "uint32" + } + ] + }, { "type": "function", "name": "grantRole", @@ -4516,6 +4544,11 @@ "name": "initiateRitual", "stateMutability": "nonpayable", "inputs": [ + { + "name": "feeModel", + "type": "address", + "internalType": "contract IFeeModel" + }, { "name": "providers", "type": "address[]", @@ -4574,19 +4607,6 @@ } ] }, - { - "type": "function", - "name": "isInitiationPublic", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bool", - "internalType": "bool" - } - ] - }, { "type": "function", "name": "isParticipant", @@ -4649,13 +4669,6 @@ } ] }, - { - "type": "function", - "name": "makeInitiationPublic", - "stateMutability": "nonpayable", - "inputs": [], - "outputs": [] - }, { "type": "function", "name": "maxDkgSize", @@ -4731,25 +4744,6 @@ } ] }, - { - "type": "function", - "name": "pendingFees", - "stateMutability": "view", - "inputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ] - }, { "type": "function", "name": "postAggregation", @@ -4808,25 +4802,6 @@ ], "outputs": [] }, - { - "type": "function", - "name": "processPendingFee", - "stateMutability": "nonpayable", - "inputs": [ - { - "name": "ritualId", - "type": "uint32", - "internalType": "uint32" - } - ], - "outputs": [ - { - "name": "refundableFee", - "type": "uint256", - "internalType": "uint256" - } - ] - }, { "type": "function", "name": "renounceRole", @@ -4946,6 +4921,11 @@ "name": "aggregatedTranscript", "type": "bytes", "internalType": "bytes" + }, + { + "name": "feeModel", + "type": "address", + "internalType": "contract IFeeModel" } ] }, @@ -5057,19 +5037,6 @@ } ] }, - { - "type": "function", - "name": "totalPendingFees", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ] - }, { "type": "function", "name": "transferRitualAuthority", @@ -5087,24 +5054,6 @@ } ], "outputs": [] - }, - { - "type": "function", - "name": "withdrawTokens", - "stateMutability": "nonpayable", - "inputs": [ - { - "name": "token", - "type": "address", - "internalType": "contract IERC20" - }, - { - "name": "amount", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [] } ], "tx_hash": "0x4207f1fd038945ef7d5da06a0989446d3ed2eb031adad1233c7e3a61951652a1", From 23cb717cea529eed44c5065b43dea8d7fa04ee1d Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Tue, 9 Jul 2024 16:53:14 +0200 Subject: [PATCH 075/105] Rename coordinator approves BqETH fee model script --- ...pprove_fee_model.py => coordinator_approve_bqeth_fee_model.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scripts/lynx/{coordinator_approve_fee_model.py => coordinator_approve_bqeth_fee_model.py} (100%) diff --git a/scripts/lynx/coordinator_approve_fee_model.py b/scripts/lynx/coordinator_approve_bqeth_fee_model.py similarity index 100% rename from scripts/lynx/coordinator_approve_fee_model.py rename to scripts/lynx/coordinator_approve_bqeth_fee_model.py From b84fd2836aab976e03e18cf64eb1ed7504a7904e Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Mon, 15 Jul 2024 11:11:25 -0400 Subject: [PATCH 076/105] BqETHSubscription: adopter set by special role --- .../subscription/BqETHSubscription.sol | 22 +++++-- tests/test_bqeth_subscription.py | 58 ++++++++++++++++--- 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/contracts/contracts/coordination/subscription/BqETHSubscription.sol b/contracts/contracts/coordination/subscription/BqETHSubscription.sol index aa0bcd8f1..39c7246f4 100644 --- a/contracts/contracts/coordination/subscription/BqETHSubscription.sol +++ b/contracts/contracts/coordination/subscription/BqETHSubscription.sol @@ -26,7 +26,7 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable GlobalAllowList public immutable accessController; IERC20 public immutable feeToken; - address public immutable adopter; + address public immutable adopterSetter; uint256 public immutable initialBaseFeeRate; uint256 public immutable baseFeeRateIncrease; @@ -35,8 +35,9 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable uint32 public activeRitualId; mapping(uint256 periodNumber => Billing billing) public billingInfo; + address public adopter; - uint256[20] private gap; + uint256[19] private gap; /** * @notice Emitted when a subscription is spent @@ -79,7 +80,7 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable * @param _coordinator The address of the coordinator contract * @param _accessController The address of the global allow list * @param _feeToken The address of the fee token contract - * @param _adopter The address of the adopter + * @param _adopterSetter The address of the adopter * @param _initialBaseFeeRate Fee rate per node per second * @param _baseFeeRateIncrease Increase of base fee rate per each period (fraction of INCREASE_BASE) * @param _encryptorFeeRate Fee rate per encryptor per second @@ -92,7 +93,7 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable Coordinator _coordinator, GlobalAllowList _accessController, IERC20 _feeToken, - address _adopter, + address _adopterSetter, uint256 _initialBaseFeeRate, uint256 _baseFeeRateIncrease, uint256 _encryptorFeeRate, @@ -109,7 +110,7 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable ) { require(address(_feeToken) != address(0), "Fee token cannot be the zero address"); - require(_adopter != address(0), "Adopter cannot be the zero address"); + require(_adopterSetter != address(0), "Adopter setter cannot be the zero address"); require( address(_accessController) != address(0), "Access controller cannot be the zero address" @@ -119,7 +120,7 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable "Base fee rate increase must be fraction of INCREASE_BASE" ); feeToken = _feeToken; - adopter = _adopter; + adopterSetter = _adopterSetter; initialBaseFeeRate = _initialBaseFeeRate; baseFeeRateIncrease = _baseFeeRateIncrease; encryptorFeeRate = _encryptorFeeRate; @@ -152,6 +153,15 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable __Ownable_init(_treasury); } + function setAdopter(address _adopter) external { + require(msg.sender == adopterSetter, "Only authority can set adopter"); + require( + adopter == address(0) && _adopter != address(0), + "Adopter can be set only once with not zero address" + ); + adopter = _adopter; + } + function baseFees() public view returns (uint256) { uint256 currentPeriodNumber = getCurrentPeriodNumber(); return baseFees(currentPeriodNumber); diff --git a/tests/test_bqeth_subscription.py b/tests/test_bqeth_subscription.py index 44f0e2999..815b5157b 100644 --- a/tests/test_bqeth_subscription.py +++ b/tests/test_bqeth_subscription.py @@ -19,6 +19,7 @@ import ape import pytest +from ape.utils import ZERO_ADDRESS from eth_account.messages import encode_defunct from web3 import Web3 @@ -72,6 +73,11 @@ def adopter(accounts): return accounts[2] +@pytest.fixture(scope="module") +def adopter_setter(accounts): + return accounts[3] + + @pytest.fixture() def erc20(project, adopter): token = project.TestToken.deploy(ERC20_SUPPLY, sender=adopter) @@ -94,13 +100,13 @@ def global_allow_list(project, creator, coordinator): @pytest.fixture() def subscription( - project, creator, coordinator, global_allow_list, erc20, adopter, treasury, oz_dependency + project, creator, coordinator, global_allow_list, erc20, adopter_setter, treasury, oz_dependency ): contract = project.BqETHSubscription.deploy( coordinator.address, global_allow_list.address, erc20.address, - adopter, + adopter_setter, BASE_FEE_RATE, BASE_FEE_RATE_INCREASE * 100, ENCRYPTORS_FEE_RATE, @@ -124,10 +130,22 @@ def subscription( return proxy_contract +def test_adopter_setter(subscription, adopter_setter, adopter): + with ape.reverts("Only authority can set adopter"): + subscription.setAdopter(adopter, sender=adopter) + with ape.reverts("Adopter can be set only once with not zero address"): + subscription.setAdopter(ZERO_ADDRESS, sender=adopter_setter) + subscription.setAdopter(adopter, sender=adopter_setter) + assert subscription.adopter() == adopter + with ape.reverts("Adopter can be set only once with not zero address"): + subscription.setAdopter(adopter_setter, sender=adopter_setter) + + def test_pay_subscription( - erc20, subscription, coordinator, global_allow_list, adopter, treasury, chain + erc20, subscription, coordinator, global_allow_list, adopter, adopter_setter, treasury, chain ): erc20.approve(subscription.address, ERC20_SUPPLY, sender=adopter) + subscription.setAdopter(adopter, sender=adopter_setter) # First payment balance_before = erc20.balanceOf(adopter) @@ -250,7 +268,7 @@ def test_pay_subscription( def test_pay_encryptor_slots( - erc20, subscription, coordinator, global_allow_list, adopter, treasury, chain + erc20, subscription, coordinator, global_allow_list, adopter, adopter_setter, treasury, chain ): encryptor_slots = 10 assert ( @@ -259,6 +277,7 @@ def test_pay_encryptor_slots( ) erc20.approve(subscription.address, ERC20_SUPPLY, sender=adopter) + subscription.setAdopter(adopter, sender=adopter_setter) with ape.reverts("Current billing period must be paid"): subscription.payForEncryptorSlots(encryptor_slots, sender=adopter) @@ -333,8 +352,9 @@ def test_pay_encryptor_slots( subscription.payForEncryptorSlots(encryptor_slots, sender=adopter) -def test_withdraw(erc20, subscription, adopter, treasury, global_allow_list): +def test_withdraw(erc20, subscription, adopter, adopter_setter, treasury, global_allow_list): erc20.approve(subscription.address, ERC20_SUPPLY, sender=adopter) + subscription.setAdopter(adopter, sender=adopter_setter) with ape.reverts(): subscription.withdrawToTreasury(1, sender=adopter) @@ -357,10 +377,11 @@ def test_withdraw(erc20, subscription, adopter, treasury, global_allow_list): def test_process_ritual_payment( - erc20, subscription, coordinator, global_allow_list, adopter, treasury + erc20, subscription, coordinator, global_allow_list, adopter, adopter_setter, treasury ): ritual_id = 7 number_of_providers = 6 + subscription.setAdopter(adopter, sender=adopter_setter) with ape.reverts("Only the Coordinator can call this method"): subscription.processRitualPayment( @@ -460,7 +481,7 @@ def test_process_ritual_payment( def test_process_ritual_extending( - erc20, subscription, coordinator, adopter, global_allow_list, treasury + erc20, subscription, coordinator, adopter, adopter_setter, global_allow_list, treasury ): ritual_id = 6 number_of_providers = 7 @@ -475,6 +496,7 @@ def test_process_ritual_extending( ) erc20.approve(subscription.address, ERC20_SUPPLY, sender=adopter) + subscription.setAdopter(adopter, sender=adopter_setter) subscription.payForSubscription(0, sender=adopter) coordinator.setRitual( ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury @@ -535,7 +557,15 @@ def test_process_ritual_extending( def test_before_set_authorization( - erc20, subscription, coordinator, adopter, global_allow_list, treasury, creator, chain + erc20, + subscription, + coordinator, + adopter, + adopter_setter, + global_allow_list, + treasury, + creator, + chain, ): ritual_id = 6 number_of_providers = 7 @@ -547,6 +577,7 @@ def test_before_set_authorization( global_allow_list.authorize(0, [creator], sender=adopter) erc20.approve(subscription.address, ERC20_SUPPLY, sender=adopter) + subscription.setAdopter(adopter, sender=adopter_setter) subscription.payForSubscription(0, sender=adopter) coordinator.setRitual( ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury @@ -592,7 +623,15 @@ def test_before_set_authorization( def test_before_is_authorized( - erc20, subscription, coordinator, adopter, global_allow_list, treasury, creator, chain + erc20, + subscription, + coordinator, + adopter, + adopter_setter, + global_allow_list, + treasury, + creator, + chain, ): ritual_id = 6 @@ -610,6 +649,7 @@ def test_before_is_authorized( global_allow_list.isAuthorized(0, bytes(signature), bytes(data)) erc20.approve(subscription.address, ERC20_SUPPLY, sender=adopter) + subscription.setAdopter(adopter, sender=adopter_setter) subscription.payForSubscription(1, sender=adopter) coordinator.setRitual( ritual_id, RitualState.ACTIVE, 0, global_allow_list.address, sender=treasury From 5823e6120d8e77632c022d4bf1d156358e9eb50b Mon Sep 17 00:00:00 2001 From: Victoria Date: Mon, 15 Jul 2024 18:11:20 +0200 Subject: [PATCH 077/105] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Núñez --- .../coordination/subscription/BqETHSubscription.sol | 6 +++--- tests/test_bqeth_subscription.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/contracts/coordination/subscription/BqETHSubscription.sol b/contracts/contracts/coordination/subscription/BqETHSubscription.sol index 39c7246f4..5430cb8d7 100644 --- a/contracts/contracts/coordination/subscription/BqETHSubscription.sol +++ b/contracts/contracts/coordination/subscription/BqETHSubscription.sol @@ -37,7 +37,7 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable mapping(uint256 periodNumber => Billing billing) public billingInfo; address public adopter; - uint256[19] private gap; + uint256[20] private gap; /** * @notice Emitted when a subscription is spent @@ -80,7 +80,7 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable * @param _coordinator The address of the coordinator contract * @param _accessController The address of the global allow list * @param _feeToken The address of the fee token contract - * @param _adopterSetter The address of the adopter + * @param _adopterSetter Address that can set the adopter address * @param _initialBaseFeeRate Fee rate per node per second * @param _baseFeeRateIncrease Increase of base fee rate per each period (fraction of INCREASE_BASE) * @param _encryptorFeeRate Fee rate per encryptor per second @@ -154,7 +154,7 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable } function setAdopter(address _adopter) external { - require(msg.sender == adopterSetter, "Only authority can set adopter"); + require(msg.sender == adopterSetter, "Only adopter setter can set adopter"); require( adopter == address(0) && _adopter != address(0), "Adopter can be set only once with not zero address" diff --git a/tests/test_bqeth_subscription.py b/tests/test_bqeth_subscription.py index 815b5157b..352a5e1c5 100644 --- a/tests/test_bqeth_subscription.py +++ b/tests/test_bqeth_subscription.py @@ -131,7 +131,7 @@ def subscription( def test_adopter_setter(subscription, adopter_setter, adopter): - with ape.reverts("Only authority can set adopter"): + with ape.reverts("Only adopter setter can set adopter"): subscription.setAdopter(adopter, sender=adopter) with ape.reverts("Adopter can be set only once with not zero address"): subscription.setAdopter(ZERO_ADDRESS, sender=adopter_setter) From 248436e038b95017bb8ad649ec97ac725b5c2c39 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Mon, 15 Jul 2024 12:49:06 -0400 Subject: [PATCH 078/105] Fix for #292 --- .../subscription/EncryptorSlotsSubscription.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts/contracts/coordination/subscription/EncryptorSlotsSubscription.sol b/contracts/contracts/coordination/subscription/EncryptorSlotsSubscription.sol index 8522fd203..1b4c5a17a 100644 --- a/contracts/contracts/coordination/subscription/EncryptorSlotsSubscription.sol +++ b/contracts/contracts/coordination/subscription/EncryptorSlotsSubscription.sol @@ -88,7 +88,11 @@ abstract contract EncryptorSlotsSubscription is AbstractSubscription { usedEncryptorSlots += addresses.length; require(usedEncryptorSlots <= encryptorSlots, "Encryptors slots filled up"); } else { - usedEncryptorSlots -= addresses.length; + if (usedEncryptorSlots >= addresses.length) { + usedEncryptorSlots -= addresses.length; + } else { + usedEncryptorSlots = 0; + } } } From ce74d731e45e45a4f567d5ee6774f7d1cec14132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 16 Jul 2024 10:41:49 +0200 Subject: [PATCH 079/105] Remove MAX_AUTH_ACTIONS restriction --- contracts/contracts/coordination/GlobalAllowList.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/contracts/contracts/coordination/GlobalAllowList.sol b/contracts/contracts/coordination/GlobalAllowList.sol index 656073be2..b49fa60d3 100644 --- a/contracts/contracts/coordination/GlobalAllowList.sol +++ b/contracts/contracts/coordination/GlobalAllowList.sol @@ -22,8 +22,6 @@ contract GlobalAllowList is IEncryptionAuthorizer { mapping(uint32 => uint256) public authActions; - uint32 public constant MAX_AUTH_ACTIONS = 100; - /** * @notice Emitted when an address authorization is set * @param ritualId The ID of the ritual @@ -153,8 +151,6 @@ contract GlobalAllowList is IEncryptionAuthorizer { function setAuthorizations(uint32 ritualId, address[] calldata addresses, bool value) internal { require(coordinator.isRitualActive(ritualId), "Only active rituals can set authorizations"); - require(addresses.length <= MAX_AUTH_ACTIONS, "Too many addresses"); - _beforeSetAuthorization(ritualId, addresses, value); for (uint256 i = 0; i < addresses.length; i++) { bytes32 lookupKey = LookupKey.lookupKey(ritualId, addresses[i]); From b1003046b6d570bfd66f88a5651c40d3ef1693fb Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Tue, 16 Jul 2024 15:40:56 +0200 Subject: [PATCH 080/105] Revert "Remove MAX_AUTH_ACTIONS restriction" This reverts commit d2ae1da82206d71f313ea90660e34d31b5b67468. --- contracts/contracts/coordination/GlobalAllowList.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/contracts/coordination/GlobalAllowList.sol b/contracts/contracts/coordination/GlobalAllowList.sol index b49fa60d3..656073be2 100644 --- a/contracts/contracts/coordination/GlobalAllowList.sol +++ b/contracts/contracts/coordination/GlobalAllowList.sol @@ -22,6 +22,8 @@ contract GlobalAllowList is IEncryptionAuthorizer { mapping(uint32 => uint256) public authActions; + uint32 public constant MAX_AUTH_ACTIONS = 100; + /** * @notice Emitted when an address authorization is set * @param ritualId The ID of the ritual @@ -151,6 +153,8 @@ contract GlobalAllowList is IEncryptionAuthorizer { function setAuthorizations(uint32 ritualId, address[] calldata addresses, bool value) internal { require(coordinator.isRitualActive(ritualId), "Only active rituals can set authorizations"); + require(addresses.length <= MAX_AUTH_ACTIONS, "Too many addresses"); + _beforeSetAuthorization(ritualId, addresses, value); for (uint256 i = 0; i < addresses.length; i++) { bytes32 lookupKey = LookupKey.lookupKey(ritualId, addresses[i]); From 9224788be25f9b99d5952f29b65354367cccaf15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 16 Jul 2024 11:04:18 +0200 Subject: [PATCH 081/105] Adapt BqETHSubscription deploy script to addition of adopterSetter --- deployment/constructor_params/lynx/bqeth.yml | 2 +- scripts/lynx/deploy_bqeth.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/deployment/constructor_params/lynx/bqeth.yml b/deployment/constructor_params/lynx/bqeth.yml index 6755d3705..ee3a1e2c9 100644 --- a/deployment/constructor_params/lynx/bqeth.yml +++ b/deployment/constructor_params/lynx/bqeth.yml @@ -41,7 +41,7 @@ contracts: _coordinator: $COORDINATOR_PROXY _accessController: $GlobalAllowList _feeToken: $LYNX_RITUAL_TOKEN - _adopter: $LYNX_DEPLOYER + _adopterSetter: $LYNX_DEPLOYER _initialBaseFeeRate: $INITIAL_BASE_FEE_RATE _baseFeeRateIncrease: $BASE_FEE_RATE_INCREASE _encryptorFeeRate: $ENCRYPTOR_FEE_RATE diff --git a/scripts/lynx/deploy_bqeth.py b/scripts/lynx/deploy_bqeth.py index 51ab62f26..5f94541c1 100644 --- a/scripts/lynx/deploy_bqeth.py +++ b/scripts/lynx/deploy_bqeth.py @@ -23,6 +23,9 @@ def main(): deployments = [global_allow_list, bqeth_subscription] deployer.finalize(deployments=deployments) + + deployer.transact(bqeth_subscription.setAdopter, deployer.get_account().address) + merge_registries( registry_1_filepath=LYNX_REGISTRY, registry_2_filepath=deployer.registry_filepath, From c086b2ef69a59d5e2b680e8e5ef2beea388bae06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 16 Jul 2024 11:04:46 +0200 Subject: [PATCH 082/105] On mainnet, adopter setter will be NuCo --- deployment/constructor_params/mainnet/bqeth.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/deployment/constructor_params/mainnet/bqeth.yml b/deployment/constructor_params/mainnet/bqeth.yml index cc319096c..c5162a644 100644 --- a/deployment/constructor_params/mainnet/bqeth.yml +++ b/deployment/constructor_params/mainnet/bqeth.yml @@ -10,7 +10,9 @@ constants: # See deployment/artifacts/mainnet.json COORDINATOR_PROXY: "0xE74259e3dafe30bAA8700238e324b47aC98FE755" - BQETH_ADOPTER: "TBD" + # See https://github.com/nucypher/tdec/issues/137#issuecomment-1881525878 + # and https://app.safe.global/home?safe=matic:0x861aa915C785dEe04684444560fC7A2AB43a1543 + NUCO_MULTISIG: "0x861aa915C785dEe04684444560fC7A2AB43a1543" # DAI Token on Polygon PoS - References: # - https://polygonscan.com/token/0x8f3cf7ad23cd3cadbd9735aff958023239c6a063 @@ -47,7 +49,7 @@ contracts: _coordinator: $COORDINATOR_PROXY _accessController: $GlobalAllowList _feeToken: $DAI_ON_POLYGON - _adopter: $BQETH_ADOPTER + _adopterSetter: $NUCO_MULTISIG _initialBaseFeeRate: $INITIAL_BASE_FEE_RATE _baseFeeRateIncrease: $BASE_FEE_RATE_INCREASE_PER_PERIOD _encryptorFeeRate: $ENCRYPTOR_FEE_RATE From 33d3f72ffa3d36fc3ed2bee1fdb8b55c143746c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 16 Jul 2024 11:05:15 +0200 Subject: [PATCH 083/105] BqETH subscription period is 183 days --- deployment/constructor_params/mainnet/bqeth.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/constructor_params/mainnet/bqeth.yml b/deployment/constructor_params/mainnet/bqeth.yml index c5162a644..2bb4564f6 100644 --- a/deployment/constructor_params/mainnet/bqeth.yml +++ b/deployment/constructor_params/mainnet/bqeth.yml @@ -33,7 +33,7 @@ constants: ENCRYPTOR_FEE_RATE: 63419583967 # $2 per year, in DAI units per second (in Python: 2 * 10**18 // 86400 // 365) BASE_FEE_RATE_INCREASE_PER_PERIOD: 247 # 5%/year ~ 2.47%/semester, expressed in basis points (0.01%) # - Duration parameters --> 1 period = 6 months - SIX_MONTHS_IN_SECONDS: 15552000 # 180 days (~6 months) + SIX_MONTHS_IN_SECONDS: 15811200 # 183 days (~6 months) THREE_MONTHS_IN_SECONDS: 7776000 # 90 day (~3 months) contracts: From 470da448cd63ed179a0ae593713d3d7b94cce232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 16 Jul 2024 12:30:10 +0200 Subject: [PATCH 084/105] Actually use bqeth deployment parameters! --- scripts/mainnet/deploy_bqeth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/mainnet/deploy_bqeth.py b/scripts/mainnet/deploy_bqeth.py index 4a9a464d5..b7dc91b04 100644 --- a/scripts/mainnet/deploy_bqeth.py +++ b/scripts/mainnet/deploy_bqeth.py @@ -9,7 +9,7 @@ from deployment.registry import merge_registries VERIFY = False -CONSTRUCTOR_PARAMS_FILEPATH = CONSTRUCTOR_PARAMS_DIR / "mainnet" / "beta_program_initiator.yml" +CONSTRUCTOR_PARAMS_FILEPATH = CONSTRUCTOR_PARAMS_DIR / "mainnet" / "bqeth.yml" MAINNET_REGISTRY = ARTIFACTS_DIR / "mainnet.json" From d801a1ceef92d71e5aa34e22db6fa97eeb9da77b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 16 Jul 2024 12:30:22 +0200 Subject: [PATCH 085/105] Remove beta program initiator parameters --- .../tapir/beta_program_initiator.yml | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 deployment/constructor_params/tapir/beta_program_initiator.yml diff --git a/deployment/constructor_params/tapir/beta_program_initiator.yml b/deployment/constructor_params/tapir/beta_program_initiator.yml deleted file mode 100644 index 539fa08d3..000000000 --- a/deployment/constructor_params/tapir/beta_program_initiator.yml +++ /dev/null @@ -1,20 +0,0 @@ -deployment: - name: beta-program-initiator - chain_id: 80002 - -artifacts: - dir: ./deployment/artifacts/ - filename: beta_program_initiator_tapir.json - -constants: - # See deployment/artifacts/tapir.json - COORDINATOR_PROXY: "0xE690b6bCC0616Dc5294fF84ff4e00335cA52C388" - - # tapir deployer account - EXECUTOR: "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600" - -contracts: - - BetaProgramInitiator: - constructor: - _coordinator: $COORDINATOR_PROXY - _executor: $EXECUTOR From e6c8000939b7bf8f2b5b039ee76c86796504427c Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Tue, 16 Jul 2024 13:38:08 +0200 Subject: [PATCH 086/105] Add max nodes param to bqeth deployment file --- deployment/constructor_params/mainnet/bqeth.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/deployment/constructor_params/mainnet/bqeth.yml b/deployment/constructor_params/mainnet/bqeth.yml index 2bb4564f6..9b08c3131 100644 --- a/deployment/constructor_params/mainnet/bqeth.yml +++ b/deployment/constructor_params/mainnet/bqeth.yml @@ -9,7 +9,7 @@ artifacts: constants: # See deployment/artifacts/mainnet.json COORDINATOR_PROXY: "0xE74259e3dafe30bAA8700238e324b47aC98FE755" - + # See https://github.com/nucypher/tdec/issues/137#issuecomment-1881525878 # and https://app.safe.global/home?safe=matic:0x861aa915C785dEe04684444560fC7A2AB43a1543 NUCO_MULTISIG: "0x861aa915C785dEe04684444560fC7A2AB43a1543" @@ -25,12 +25,14 @@ constants: TREASURY_GUILD_ON_POLYGON: "0xc3Bf49eBA094AF346830dF4dbB42a07dE378EeB6" THRESHOLD_COUNCIL_ON_POLYGON: "0x9F6e831c8F8939DC0C830C6e492e7cEf4f9C2F5f" - # Subscription Parameters + # Subscription Parameters # See https://github.com/nucypher/tdec/issues/169 + MAX_NODES: 30 + # - Fee parameters: INITIAL_BASE_FEE_RATE: 4050925925925 # $0.35 per day, in DAI units per second (in Python: 35*10**16 // 86400) - ENCRYPTOR_FEE_RATE: 63419583967 # $2 per year, in DAI units per second (in Python: 2 * 10**18 // 86400 // 365) + ENCRYPTOR_FEE_RATE: 63419583967 # $2 per year, in DAI units per second (in Python: 2 * 10**18 // 86400 // 365) BASE_FEE_RATE_INCREASE_PER_PERIOD: 247 # 5%/year ~ 2.47%/semester, expressed in basis points (0.01%) # - Duration parameters --> 1 period = 6 months SIX_MONTHS_IN_SECONDS: 15811200 # 183 days (~6 months) From 550eb4eda877bab6f3b8e9524a575e927e1caad1 Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Wed, 17 Jul 2024 17:05:09 +0200 Subject: [PATCH 087/105] Add Free Fee Model contract deployment script --- .../mainnet/free-fee-model.yml | 10 +++++++++ scripts/mainnet/deploy_free_fee_model.py | 22 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 deployment/constructor_params/mainnet/free-fee-model.yml create mode 100644 scripts/mainnet/deploy_free_fee_model.py diff --git a/deployment/constructor_params/mainnet/free-fee-model.yml b/deployment/constructor_params/mainnet/free-fee-model.yml new file mode 100644 index 000000000..d8bc299a6 --- /dev/null +++ b/deployment/constructor_params/mainnet/free-fee-model.yml @@ -0,0 +1,10 @@ +deployment: + name: mainnet-free-fee-model + chain_id: 137 + +artifacts: + dir: ./deployment/artifacts/ + filename: free-fee-model.json + +contracts: + - FreeFeeModel diff --git a/scripts/mainnet/deploy_free_fee_model.py b/scripts/mainnet/deploy_free_fee_model.py new file mode 100644 index 000000000..542791a4f --- /dev/null +++ b/scripts/mainnet/deploy_free_fee_model.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 + +from ape import project + +from deployment.constants import ( + CONSTRUCTOR_PARAMS_DIR, ARTIFACTS_DIR, +) +from deployment.params import Deployer + +VERIFY = False +CONSTRUCTOR_PARAMS_FILEPATH = CONSTRUCTOR_PARAMS_DIR / "mainnet" / "free-fee-model.yml" +LYNX_REGISTRY = ARTIFACTS_DIR / "free-fee-model.json" + + +def main(): + deployer = Deployer.from_yaml(filepath=CONSTRUCTOR_PARAMS_FILEPATH, verify=VERIFY) + + free_fee_model = deployer.deploy(project.FreeFeeModel) + + deployments = [free_fee_model] + + deployer.finalize(deployments=deployments) From 3115e649a3fee66d2c3b50be98a512462bc55e46 Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Wed, 17 Jul 2024 09:59:54 +0200 Subject: [PATCH 088/105] Add mainnet deployment artifacts for BqETH --- deployment/artifacts/mainnet.json | 1196 ++++++++++++++++++++++++----- 1 file changed, 992 insertions(+), 204 deletions(-) diff --git a/deployment/artifacts/mainnet.json b/deployment/artifacts/mainnet.json index 282a8edb8..5c1fdcffc 100644 --- a/deployment/artifacts/mainnet.json +++ b/deployment/artifacts/mainnet.json @@ -1950,28 +1950,825 @@ "block_number": 52701776, "deployer": "0x1591165F1BF8B73de7053A6BE6f239BC15076879" }, - "Coordinator": { - "address": "0xE74259e3dafe30bAA8700238e324b47aC98FE755", + "BqETHSubscription": { + "address": "0x91c904D655e17daD2b6c1840b15696A674744446", "abi": [ { "type": "constructor", "stateMutability": "nonpayable", "inputs": [ { - "name": "_application", + "name": "_coordinator", + "type": "address", + "internalType": "contract Coordinator" + }, + { + "name": "_accessController", + "type": "address", + "internalType": "contract GlobalAllowList" + }, + { + "name": "_feeToken", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "_adopterSetter", + "type": "address", + "internalType": "address" + }, + { + "name": "_initialBaseFeeRate", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_baseFeeRateIncrease", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_encryptorFeeRate", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_maxNodes", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_subscriptionPeriodDuration", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "_yellowPeriodDuration", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "_redPeriodDuration", + "type": "uint32", + "internalType": "uint32" + } + ] + }, + { + "type": "error", + "name": "AddressEmptyCode", + "inputs": [ + { + "name": "target", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "AddressInsufficientBalance", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "FailedInnerCall", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidInitialization", + "inputs": [] + }, + { + "type": "error", + "name": "NotInitializing", + "inputs": [] + }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "SafeERC20FailedOperation", + "inputs": [ + { + "name": "token", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "event", + "name": "EncryptorSlotsPaid", + "inputs": [ + { + "name": "sponsor", + "type": "address", + "internalType": "address", + "indexed": true + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256", + "indexed": false + }, + { + "name": "encryptorSlots", + "type": "uint128", + "internalType": "uint128", + "indexed": false + }, + { + "name": "endOfCurrentPeriod", + "type": "uint32", + "internalType": "uint32", + "indexed": false + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint64", + "internalType": "uint64", + "indexed": false + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "internalType": "address", + "indexed": true + }, + { + "name": "newOwner", + "type": "address", + "internalType": "address", + "indexed": true + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SubscriptionPaid", + "inputs": [ + { + "name": "subscriber", + "type": "address", + "internalType": "address", + "indexed": true + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256", + "indexed": false + }, + { + "name": "encryptorSlots", + "type": "uint128", + "internalType": "uint128", + "indexed": false + }, + { + "name": "endOfSubscription", + "type": "uint32", + "internalType": "uint32", + "indexed": false + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "WithdrawalToTreasury", + "inputs": [ + { + "name": "treasury", + "type": "address", + "internalType": "address", + "indexed": true + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256", + "indexed": false + } + ], + "anonymous": false + }, + { + "type": "function", + "name": "INACTIVE_RITUAL_ID", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint32", + "internalType": "uint32" + } + ] + }, + { + "type": "function", + "name": "INCREASE_BASE", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "function", + "name": "accessController", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract GlobalAllowList" + } + ] + }, + { + "type": "function", + "name": "activeRitualId", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint32", + "internalType": "uint32" + } + ] + }, + { + "type": "function", + "name": "adopter", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "function", + "name": "adopterSetter", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "function", + "name": "baseFeeRateIncrease", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "function", + "name": "baseFees", + "stateMutability": "view", + "inputs": [ + { + "name": "periodNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "function", + "name": "baseFees", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "function", + "name": "beforeIsAuthorized", + "stateMutability": "view", + "inputs": [ + { + "name": "ritualId", + "type": "uint32", + "internalType": "uint32" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "beforeSetAuthorization", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "ritualId", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "addresses", + "type": "address[]", + "internalType": "address[]" + }, + { + "name": "value", + "type": "bool", + "internalType": "bool" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "billingInfo", + "stateMutability": "view", + "inputs": [ + { + "name": "periodNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "paid", + "type": "bool", + "internalType": "bool" + }, + { + "name": "encryptorSlots", + "type": "uint128", + "internalType": "uint128" + } + ] + }, + { + "type": "function", + "name": "coordinator", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract Coordinator" + } + ] + }, + { + "type": "function", + "name": "encryptorFeeRate", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "function", + "name": "encryptorFees", + "stateMutability": "view", + "inputs": [ + { + "name": "encryptorSlots", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "duration", + "type": "uint32", + "internalType": "uint32" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "function", + "name": "feeToken", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IERC20" + } + ] + }, + { + "type": "function", + "name": "getCurrentPeriodNumber", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "function", + "name": "getEndOfSubscription", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "endOfSubscription", + "type": "uint32", + "internalType": "uint32" + } + ] + }, + { + "type": "function", + "name": "getPaidEncryptorSlots", + "stateMutability": "view", + "inputs": [ + { + "name": "periodNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "function", + "name": "initialBaseFeeRate", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "function", + "name": "initialize", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "_treasury", + "type": "address", + "internalType": "address" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "isPeriodPaid", + "stateMutability": "view", + "inputs": [ + { + "name": "periodNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ] + }, + { + "type": "function", + "name": "maxNodes", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "function", + "name": "owner", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "function", + "name": "payForEncryptorSlots", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "additionalEncryptorSlots", + "type": "uint128", + "internalType": "uint128" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "payForSubscription", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "encryptorSlots", + "type": "uint128", + "internalType": "uint128" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "processRitualExtending", + "stateMutability": "view", + "inputs": [ + { + "name": "", "type": "address", - "internalType": "contract ITACoChildApplication" + "internalType": "address" + }, + { + "name": "ritualId", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" }, { - "name": "_currency", + "name": "", + "type": "uint32", + "internalType": "uint32" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "processRitualPayment", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "initiator", "type": "address", - "internalType": "contract IERC20" + "internalType": "address" + }, + { + "name": "ritualId", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "numberOfProviders", + "type": "uint256", + "internalType": "uint256" }, { - "name": "_feeRatePerSecond", + "name": "duration", + "type": "uint32", + "internalType": "uint32" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "redPeriodDuration", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint32", + "internalType": "uint32" + } + ] + }, + { + "type": "function", + "name": "renounceOwnership", + "stateMutability": "nonpayable", + "inputs": [], + "outputs": [] + }, + { + "type": "function", + "name": "setAdopter", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "_adopter", + "type": "address", + "internalType": "address" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "startOfSubscription", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint32", + "internalType": "uint32" + } + ] + }, + { + "type": "function", + "name": "subscriptionPeriodDuration", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint32", + "internalType": "uint32" + } + ] + }, + { + "type": "function", + "name": "transferOwnership", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "usedEncryptorSlots", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "function", + "name": "withdrawToTreasury", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "amount", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [] + }, + { + "type": "function", + "name": "yellowPeriodDuration", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint32", + "internalType": "uint32" + } + ] + } + ], + "tx_hash": "0x13ec5b11d7866b11fe5c511fd1e1990c20643c41ff32b830ea1514d9c7e8741f", + "block_number": 59432268, + "deployer": "0x0224B7B41E9204550b8B3Fdc1afd6200446576E8" + }, + "Coordinator": { + "address": "0xE74259e3dafe30bAA8700238e324b47aC98FE755", + "abi": [ + { + "type": "constructor", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "_application", + "type": "address", + "internalType": "contract ITACoChildApplication" + } ] }, { @@ -2022,33 +2819,6 @@ } ] }, - { - "type": "error", - "name": "AddressEmptyCode", - "inputs": [ - { - "name": "target", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "AddressInsufficientBalance", - "inputs": [ - { - "name": "account", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "FailedInnerCall", - "inputs": [] - }, { "type": "error", "name": "InvalidInitialization", @@ -2075,17 +2845,6 @@ } ] }, - { - "type": "error", - "name": "SafeERC20FailedOperation", - "inputs": [ - { - "name": "token", - "type": "address", - "internalType": "address" - } - ] - }, { "type": "event", "name": "AggregationPosted", @@ -2180,6 +2939,19 @@ ], "anonymous": false }, + { + "type": "event", + "name": "FeeModelApproved", + "inputs": [ + { + "name": "feeModel", + "type": "address", + "internalType": "contract IFeeModel", + "indexed": false + } + ], + "anonymous": false + }, { "type": "event", "name": "Initialized", @@ -2292,6 +3064,25 @@ ], "anonymous": false }, + { + "type": "event", + "name": "RitualExtended", + "inputs": [ + { + "name": "ritualId", + "type": "uint32", + "internalType": "uint32", + "indexed": true + }, + { + "name": "endTimestamp", + "type": "uint32", + "internalType": "uint32", + "indexed": false + } + ], + "anonymous": false + }, { "type": "event", "name": "RoleAdminChanged", @@ -2462,19 +3253,6 @@ } ] }, - { - "type": "function", - "name": "INITIATOR_ROLE", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ] - }, { "type": "function", "name": "TREASURY_ROLE", @@ -2502,11 +3280,24 @@ "inputs": [], "outputs": [ { - "name": "", + "name": "", + "type": "address", + "internalType": "contract ITACoChildApplication" + } + ] + }, + { + "type": "function", + "name": "approveFeeModel", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "feeModel", "type": "address", - "internalType": "contract ITACoChildApplication" + "internalType": "contract IFeeModel" } - ] + ], + "outputs": [] }, { "type": "function", @@ -2562,33 +3353,33 @@ }, { "type": "function", - "name": "currency", + "name": "defaultAdmin", "stateMutability": "view", "inputs": [], "outputs": [ { "name": "", "type": "address", - "internalType": "contract IERC20" + "internalType": "address" } ] }, { "type": "function", - "name": "defaultAdmin", + "name": "defaultAdminDelay", "stateMutability": "view", "inputs": [], "outputs": [ { "name": "", - "type": "address", - "internalType": "address" + "type": "uint48", + "internalType": "uint48" } ] }, { "type": "function", - "name": "defaultAdminDelay", + "name": "defaultAdminDelayIncreaseWait", "stateMutability": "view", "inputs": [], "outputs": [ @@ -2601,57 +3392,101 @@ }, { "type": "function", - "name": "defaultAdminDelayIncreaseWait", + "name": "extendRitual", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "ritualId", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "duration", + "type": "uint32", + "internalType": "uint32" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "feeModelsRegistry", "stateMutability": "view", - "inputs": [], + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IFeeModel" + } + ], "outputs": [ { "name": "", - "type": "uint48", - "internalType": "uint48" + "type": "bool", + "internalType": "bool" } ] }, { "type": "function", - "name": "feeDeduction", - "stateMutability": "pure", + "name": "getAccessController", + "stateMutability": "view", "inputs": [ { - "name": "", - "type": "uint256", - "internalType": "uint256" - }, + "name": "ritualId", + "type": "uint32", + "internalType": "uint32" + } + ], + "outputs": [ { "name": "", - "type": "uint256", - "internalType": "uint256" + "type": "address", + "internalType": "contract IEncryptionAuthorizer" + } + ] + }, + { + "type": "function", + "name": "getAuthority", + "stateMutability": "view", + "inputs": [ + { + "name": "ritualId", + "type": "uint32", + "internalType": "uint32" } ], "outputs": [ { "name": "", - "type": "uint256", - "internalType": "uint256" + "type": "address", + "internalType": "address" } ] }, { "type": "function", - "name": "feeRatePerSecond", + "name": "getFeeModel", "stateMutability": "view", - "inputs": [], + "inputs": [ + { + "name": "ritualId", + "type": "uint32", + "internalType": "uint32" + } + ], "outputs": [ { "name": "", - "type": "uint256", - "internalType": "uint256" + "type": "address", + "internalType": "contract IFeeModel" } ] }, { "type": "function", - "name": "getAuthority", + "name": "getInitiator", "stateMutability": "view", "inputs": [ { @@ -2984,30 +3819,6 @@ } ] }, - { - "type": "function", - "name": "getRitualInitiationCost", - "stateMutability": "view", - "inputs": [ - { - "name": "providers", - "type": "address[]", - "internalType": "address[]" - }, - { - "name": "duration", - "type": "uint32", - "internalType": "uint32" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ] - }, { "type": "function", "name": "getRitualState", @@ -3065,6 +3876,30 @@ } ] }, + { + "type": "function", + "name": "getTimestamps", + "stateMutability": "view", + "inputs": [ + { + "name": "ritualId", + "type": "uint32", + "internalType": "uint32" + } + ], + "outputs": [ + { + "name": "initTimestamp", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "endTimestamp", + "type": "uint32", + "internalType": "uint32" + } + ] + }, { "type": "function", "name": "grantRole", @@ -3135,6 +3970,11 @@ "name": "initiateRitual", "stateMutability": "nonpayable", "inputs": [ + { + "name": "feeModel", + "type": "address", + "internalType": "contract IFeeModel" + }, { "name": "providers", "type": "address[]", @@ -3193,19 +4033,6 @@ } ] }, - { - "type": "function", - "name": "isInitiationPublic", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bool", - "internalType": "bool" - } - ] - }, { "type": "function", "name": "isParticipant", @@ -3268,13 +4095,6 @@ } ] }, - { - "type": "function", - "name": "makeInitiationPublic", - "stateMutability": "nonpayable", - "inputs": [], - "outputs": [] - }, { "type": "function", "name": "maxDkgSize", @@ -3350,25 +4170,6 @@ } ] }, - { - "type": "function", - "name": "pendingFees", - "stateMutability": "view", - "inputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ] - }, { "type": "function", "name": "postAggregation", @@ -3427,25 +4228,6 @@ ], "outputs": [] }, - { - "type": "function", - "name": "processPendingFee", - "stateMutability": "nonpayable", - "inputs": [ - { - "name": "ritualId", - "type": "uint32", - "internalType": "uint32" - } - ], - "outputs": [ - { - "name": "refundableFee", - "type": "uint256", - "internalType": "uint256" - } - ] - }, { "type": "function", "name": "renounceRole", @@ -3565,6 +4347,11 @@ "name": "aggregatedTranscript", "type": "bytes", "internalType": "bytes" + }, + { + "name": "feeModel", + "type": "address", + "internalType": "contract IFeeModel" } ] }, @@ -3676,19 +4463,6 @@ } ] }, - { - "type": "function", - "name": "totalPendingFees", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ] - }, { "type": "function", "name": "transferRitualAuthority", @@ -3706,24 +4480,6 @@ } ], "outputs": [] - }, - { - "type": "function", - "name": "withdrawTokens", - "stateMutability": "nonpayable", - "inputs": [ - { - "name": "token", - "type": "address", - "internalType": "contract IERC20" - }, - { - "name": "amount", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [] } ], "tx_hash": "0x2a7f64225d9884b6b854ba7454283ecd4730785cbc862b72d4d8111224688e13", @@ -3731,7 +4487,7 @@ "deployer": "0xFfFd7092685bDeeBD121D1A0FEA3c349114Cce50" }, "GlobalAllowList": { - "address": "0xa8D488019F6627C4eA806242CbEc06EaF7CfA03c", + "address": "0x6D25F454D9FDE0d9b71f34965cb53316e6549c94", "abi": [ { "type": "constructor", @@ -3796,6 +4552,38 @@ ], "anonymous": false }, + { + "type": "function", + "name": "MAX_AUTH_ACTIONS", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint32", + "internalType": "uint32" + } + ] + }, + { + "type": "function", + "name": "authActions", + "stateMutability": "view", + "inputs": [ + { + "name": "", + "type": "uint32", + "internalType": "uint32" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ] + }, { "type": "function", "name": "authorize", @@ -3899,9 +4687,9 @@ ] } ], - "tx_hash": "0x8b394cc41255d7b580d1561c5236b020ec2048782a1e7553f6f476f06ea61c13", - "block_number": 50224144, - "deployer": "0xFfFd7092685bDeeBD121D1A0FEA3c349114Cce50" + "tx_hash": "0xac7ce9605d9b11ef7ec01426c739768c3c2d15604b653f7063ea0e2218da5a62", + "block_number": 59432250, + "deployer": "0x0224B7B41E9204550b8B3Fdc1afd6200446576E8" }, "PolygonChild": { "address": "0x1f5C5fd6A66723fA22a778CC53263dd3FA6851E5", @@ -5052,4 +5840,4 @@ "deployer": "0xFfFd7092685bDeeBD121D1A0FEA3c349114Cce50" } } -} \ No newline at end of file +} From 9ae1ab52fdab71bb1ad28643ccda76fc5c4a9972 Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Wed, 17 Jul 2024 17:08:56 +0200 Subject: [PATCH 089/105] Add mainnet deployment artifact of FreeFeeModel --- deployment/artifacts/mainnet.json | 211 ++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) diff --git a/deployment/artifacts/mainnet.json b/deployment/artifacts/mainnet.json index 5c1fdcffc..0ca6ce8ac 100644 --- a/deployment/artifacts/mainnet.json +++ b/deployment/artifacts/mainnet.json @@ -4486,6 +4486,217 @@ "block_number": 50224074, "deployer": "0xFfFd7092685bDeeBD121D1A0FEA3c349114Cce50" }, + "FreeFeeModel": { + "address": "0x1acaf2677B987e690A09296BabdCe6376712213d", + "abi": [ + { + "type": "constructor", + "stateMutability": "nonpayable", + "inputs": [] + }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "internalType": "address", + "indexed": true + }, + { + "name": "newOwner", + "type": "address", + "internalType": "address", + "indexed": true + } + ], + "anonymous": false + }, + { + "type": "function", + "name": "approveInitiator", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "initiator", + "type": "address", + "internalType": "address" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "beforeIsAuthorized", + "stateMutability": "view", + "inputs": [ + { + "name": "ritualId", + "type": "uint32", + "internalType": "uint32" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "beforeSetAuthorization", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "ritualId", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "addresses", + "type": "address[]", + "internalType": "address[]" + }, + { + "name": "value", + "type": "bool", + "internalType": "bool" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "initiatorWhiteList", + "stateMutability": "view", + "inputs": [ + { + "name": "initiator", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "approved", + "type": "bool", + "internalType": "bool" + } + ] + }, + { + "type": "function", + "name": "owner", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "function", + "name": "processRitualExtending", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "initiator", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "uint32", + "internalType": "uint32" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "processRitualPayment", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "initiator", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "uint32", + "internalType": "uint32" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "renounceOwnership", + "stateMutability": "nonpayable", + "inputs": [], + "outputs": [] + }, + { + "type": "function", + "name": "transferOwnership", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [] + } + ], + "tx_hash": "0xaefffed758247637a234c1f3c1cbc529f95f5a4b31165ec5fea7f74ac07204ba", + "block_number": 59473269, + "deployer": "0x0224B7B41E9204550b8B3Fdc1afd6200446576E8" + }, "GlobalAllowList": { "address": "0x6D25F454D9FDE0d9b71f34965cb53316e6549c94", "abi": [ From 51b28f938b557b8e3d589d08e4f0215c67732876 Mon Sep 17 00:00:00 2001 From: KPrasch Date: Fri, 2 Aug 2024 21:47:28 +0700 Subject: [PATCH 090/105] remove BetaProgramInitiator from mainnet registry --- deployment/artifacts/mainnet.json | 369 ------------------------------ 1 file changed, 369 deletions(-) diff --git a/deployment/artifacts/mainnet.json b/deployment/artifacts/mainnet.json index 0ca6ce8ac..78a2b75c5 100644 --- a/deployment/artifacts/mainnet.json +++ b/deployment/artifacts/mainnet.json @@ -1581,375 +1581,6 @@ } }, "137": { - "BetaProgramInitiator": { - "address": "0x7CEbC88351061b2721865f01d2aCEc4c3eC92E8d", - "abi": [ - { - "type": "constructor", - "stateMutability": "nonpayable", - "inputs": [ - { - "name": "_coordinator", - "type": "address", - "internalType": "contract Coordinator" - }, - { - "name": "_executor", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "AddressEmptyCode", - "inputs": [ - { - "name": "target", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "AddressInsufficientBalance", - "inputs": [ - { - "name": "account", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "FailedInnerCall", - "inputs": [] - }, - { - "type": "error", - "name": "SafeERC20FailedOperation", - "inputs": [ - { - "name": "token", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "event", - "name": "FailedRequestRefunded", - "inputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256", - "indexed": true - }, - { - "name": "refundAmount", - "type": "uint256", - "internalType": "uint256", - "indexed": false - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "RequestCanceled", - "inputs": [ - { - "name": "sender", - "type": "address", - "internalType": "address", - "indexed": true - }, - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256", - "indexed": true - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "RequestExecuted", - "inputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256", - "indexed": true - }, - { - "name": "ritualId", - "type": "uint256", - "internalType": "uint256", - "indexed": true - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "RequestRegistered", - "inputs": [ - { - "name": "sender", - "type": "address", - "internalType": "address", - "indexed": true - }, - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256", - "indexed": true - }, - { - "name": "providers", - "type": "address[]", - "internalType": "address[]", - "indexed": false - }, - { - "name": "authority", - "type": "address", - "internalType": "address", - "indexed": false - }, - { - "name": "duration", - "type": "uint32", - "internalType": "uint32", - "indexed": false - }, - { - "name": "accessController", - "type": "address", - "internalType": "contract IEncryptionAuthorizer", - "indexed": false - }, - { - "name": "payment", - "type": "uint256", - "internalType": "uint256", - "indexed": false - } - ], - "anonymous": false - }, - { - "type": "function", - "name": "NO_RITUAL", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint32", - "internalType": "uint32" - } - ] - }, - { - "type": "function", - "name": "cancelInitiationRequest", - "stateMutability": "nonpayable", - "inputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [] - }, - { - "type": "function", - "name": "coordinator", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "contract Coordinator" - } - ] - }, - { - "type": "function", - "name": "currency", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "contract IERC20" - } - ] - }, - { - "type": "function", - "name": "executeInitiationRequest", - "stateMutability": "nonpayable", - "inputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [] - }, - { - "type": "function", - "name": "executor", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "function", - "name": "getProviders", - "stateMutability": "view", - "inputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "address[]", - "internalType": "address[]" - } - ] - }, - { - "type": "function", - "name": "getRequestsLength", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "function", - "name": "refundFailedRequest", - "stateMutability": "nonpayable", - "inputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [] - }, - { - "type": "function", - "name": "registerInitiationRequest", - "stateMutability": "nonpayable", - "inputs": [ - { - "name": "providers", - "type": "address[]", - "internalType": "address[]" - }, - { - "name": "authority", - "type": "address", - "internalType": "address" - }, - { - "name": "duration", - "type": "uint32", - "internalType": "uint32" - }, - { - "name": "accessController", - "type": "address", - "internalType": "contract IEncryptionAuthorizer" - } - ], - "outputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "function", - "name": "requests", - "stateMutability": "view", - "inputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "authority", - "type": "address", - "internalType": "address" - }, - { - "name": "duration", - "type": "uint32", - "internalType": "uint32" - }, - { - "name": "accessController", - "type": "address", - "internalType": "contract IEncryptionAuthorizer" - }, - { - "name": "sender", - "type": "address", - "internalType": "address" - }, - { - "name": "ritualId", - "type": "uint32", - "internalType": "uint32" - }, - { - "name": "payment", - "type": "uint256", - "internalType": "uint256" - } - ] - } - ], - "tx_hash": "0xca501b1153acf6d47a2def2413fc5c72302f6d87c050f4e859eacc888e3fc492", - "block_number": 52701776, - "deployer": "0x1591165F1BF8B73de7053A6BE6f239BC15076879" - }, "BqETHSubscription": { "address": "0x91c904D655e17daD2b6c1840b15696A674744446", "abi": [ From 2b27417716c70ade753dd498a849d711acd8d326 Mon Sep 17 00:00:00 2001 From: KPrasch Date: Mon, 5 Aug 2024 15:53:49 +0700 Subject: [PATCH 091/105] removes BetaProgramInitiator registry entry for Tapir testnet. --- deployment/artifacts/tapir.json | 369 -------------------------------- 1 file changed, 369 deletions(-) diff --git a/deployment/artifacts/tapir.json b/deployment/artifacts/tapir.json index 7805a95fc..af416637d 100644 --- a/deployment/artifacts/tapir.json +++ b/deployment/artifacts/tapir.json @@ -2335,375 +2335,6 @@ } }, "80002": { - "BetaProgramInitiator": { - "address": "0x418e991fD07cfA950855F820023AF6191E18B6df", - "abi": [ - { - "type": "constructor", - "stateMutability": "nonpayable", - "inputs": [ - { - "name": "_coordinator", - "type": "address", - "internalType": "contract Coordinator" - }, - { - "name": "_executor", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "AddressEmptyCode", - "inputs": [ - { - "name": "target", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "AddressInsufficientBalance", - "inputs": [ - { - "name": "account", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "FailedInnerCall", - "inputs": [] - }, - { - "type": "error", - "name": "SafeERC20FailedOperation", - "inputs": [ - { - "name": "token", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "event", - "name": "FailedRequestRefunded", - "inputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256", - "indexed": true - }, - { - "name": "refundAmount", - "type": "uint256", - "internalType": "uint256", - "indexed": false - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "RequestCanceled", - "inputs": [ - { - "name": "sender", - "type": "address", - "internalType": "address", - "indexed": true - }, - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256", - "indexed": true - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "RequestExecuted", - "inputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256", - "indexed": true - }, - { - "name": "ritualId", - "type": "uint256", - "internalType": "uint256", - "indexed": true - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "RequestRegistered", - "inputs": [ - { - "name": "sender", - "type": "address", - "internalType": "address", - "indexed": true - }, - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256", - "indexed": true - }, - { - "name": "providers", - "type": "address[]", - "internalType": "address[]", - "indexed": false - }, - { - "name": "authority", - "type": "address", - "internalType": "address", - "indexed": false - }, - { - "name": "duration", - "type": "uint32", - "internalType": "uint32", - "indexed": false - }, - { - "name": "accessController", - "type": "address", - "internalType": "contract IEncryptionAuthorizer", - "indexed": false - }, - { - "name": "payment", - "type": "uint256", - "internalType": "uint256", - "indexed": false - } - ], - "anonymous": false - }, - { - "type": "function", - "name": "NO_RITUAL", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint32", - "internalType": "uint32" - } - ] - }, - { - "type": "function", - "name": "cancelInitiationRequest", - "stateMutability": "nonpayable", - "inputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [] - }, - { - "type": "function", - "name": "coordinator", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "contract Coordinator" - } - ] - }, - { - "type": "function", - "name": "currency", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "contract IERC20" - } - ] - }, - { - "type": "function", - "name": "executeInitiationRequest", - "stateMutability": "nonpayable", - "inputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [] - }, - { - "type": "function", - "name": "executor", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "function", - "name": "getProviders", - "stateMutability": "view", - "inputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "address[]", - "internalType": "address[]" - } - ] - }, - { - "type": "function", - "name": "getRequestsLength", - "stateMutability": "view", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "function", - "name": "refundFailedRequest", - "stateMutability": "nonpayable", - "inputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [] - }, - { - "type": "function", - "name": "registerInitiationRequest", - "stateMutability": "nonpayable", - "inputs": [ - { - "name": "providers", - "type": "address[]", - "internalType": "address[]" - }, - { - "name": "authority", - "type": "address", - "internalType": "address" - }, - { - "name": "duration", - "type": "uint32", - "internalType": "uint32" - }, - { - "name": "accessController", - "type": "address", - "internalType": "contract IEncryptionAuthorizer" - } - ], - "outputs": [ - { - "name": "requestIndex", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "function", - "name": "requests", - "stateMutability": "view", - "inputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "authority", - "type": "address", - "internalType": "address" - }, - { - "name": "duration", - "type": "uint32", - "internalType": "uint32" - }, - { - "name": "accessController", - "type": "address", - "internalType": "contract IEncryptionAuthorizer" - }, - { - "name": "sender", - "type": "address", - "internalType": "address" - }, - { - "name": "ritualId", - "type": "uint32", - "internalType": "uint32" - }, - { - "name": "payment", - "type": "uint256", - "internalType": "uint256" - } - ] - } - ], - "tx_hash": "0x21d379cc84b0b373497a5da3bd1da00a1cc8706f6efef21316cfba2a82cde7e2", - "block_number": 5393140, - "deployer": "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600" - }, "Coordinator": { "address": "0xE690b6bCC0616Dc5294fF84ff4e00335cA52C388", "abi": [ From ffa513ebd947c0ab6499c1554e52b60519f45b56 Mon Sep 17 00:00:00 2001 From: KPrasch Date: Mon, 5 Aug 2024 15:47:21 +0700 Subject: [PATCH 092/105] ape-driven generic subscription management CLI Co-authored-by: James Campbell Co-authored-by: derekpierre Co-authored-by: KPrasch --- deployment/constants.py | 15 +++ deployment/options.py | 57 +++++++++++ deployment/registry.py | 26 ++++- deployment/utils.py | 4 +- scripts/manage_subscription.py | 174 +++++++++++++++++++++++++++++++++ 5 files changed, 272 insertions(+), 4 deletions(-) create mode 100644 deployment/options.py create mode 100644 scripts/manage_subscription.py diff --git a/deployment/constants.py b/deployment/constants.py index 924516490..92964597c 100644 --- a/deployment/constants.py +++ b/deployment/constants.py @@ -12,12 +12,16 @@ # # Domains # + LYNX = "lynx" TAPIR = "tapir" MAINNET = "mainnet" SUPPORTED_TACO_DOMAINS = [LYNX, TAPIR, MAINNET] +# +# Nodes +# LYNX_NODES = { # staking provider -> operator @@ -39,3 +43,14 @@ # Admin slot - https://eips.ethereum.org/EIPS/eip-1967#admin-address EIP1967_ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103 + + +# +# Contracts +# + +ACCESS_CONTROLLERS = [ + "GlobalAllowList", + "OpenAccessAuthorizer", + "ManagedAllowList" +] diff --git a/deployment/options.py b/deployment/options.py new file mode 100644 index 000000000..75298c67f --- /dev/null +++ b/deployment/options.py @@ -0,0 +1,57 @@ +import click +from eth_typing import ChecksumAddress + +from deployment.constants import ( + ACCESS_CONTROLLERS, + SUPPORTED_TACO_DOMAINS +) + + +access_controller_option = click.option( + "--access-controller", + "-a", + help="global allow list or open access authorizer.", + type=click.Choice(ACCESS_CONTROLLERS), + required=True, +) + +domain_option = click.option( + "--domain", + "-d", + help="TACo domain", + type=click.Choice(SUPPORTED_TACO_DOMAINS), + required=True, +) + +ritual_id_option = click.option( + "--ritual-id", + "-r", + help="ID of the ritual", + required=True, + type=int +) + +subscription_contract_option = click.option( + "--subscription-contract", + "-s", + help="Name of a subscription contract", + type=click.Choice(["BqETHSubscription"]), + required=True, +) + +encryptor_slots_option = click.option( + "--encryptor-slots", + "-es", + help="Number of encryptor slots to pay for.", + required=True, + type=int +) + +encryptors_option = click.option( + "--encryptors", + "-e", + help="List of encryptor addresses to remove.", + multiple=True, + required=True, + type=ChecksumAddress +) diff --git a/deployment/registry.py b/deployment/registry.py index 8e1483efa..7fb03e45b 100644 --- a/deployment/registry.py +++ b/deployment/registry.py @@ -5,13 +5,17 @@ from pathlib import Path from typing import Dict, List, NamedTuple, Optional +from ape import chain, project from ape.contracts import ContractInstance -from ape import chain from eth_typing import ChecksumAddress from eth_utils import to_checksum_address from web3.types import ABI -from deployment.utils import _load_json, get_contract_container +from deployment.utils import ( + _load_json, + get_contract_container, + registry_filepath_from_domain +) ChainId = int ContractName = str @@ -20,6 +24,10 @@ STANDARD_REGISTRY_JSON_FORMAT = {"indent": 4, "separators": (",", ": ")} +class NoContractFound(Exception): + """Raised when a contract is not found in the registry.""" + + class RegistryEntry(NamedTuple): """Represents a single entry in a nucypher-style contract registry.""" @@ -295,3 +303,17 @@ def normalize_registry(filepath: Path): except Exception: print(f"Error when normalizing registry at {filepath}.") raise + + +def get_contract(domain: str, contract_name: str) -> ContractInstance: + """Returns the contract instance for the contract name and domain.""" + registry_filepath = registry_filepath_from_domain(domain=domain) + chain_id = project.chain_manager.chain_id + deployments = contracts_from_registry(filepath=registry_filepath, chain_id=chain_id) + try: + return deployments[contract_name] + except KeyError: + raise NoContractFound( + f"Contract '{contract_name}' not found in {domain} registry for chain {chain_id}. " + "Are you connected to the correct network + domain?" + ) diff --git a/deployment/utils.py b/deployment/utils.py index 09f99ff26..4fbfaaba6 100644 --- a/deployment/utils.py +++ b/deployment/utils.py @@ -7,6 +7,7 @@ from ape import networks, project from ape.contracts import ContractContainer, ContractInstance from ape_etherscan.utils import API_KEY_ENV_KEY_MAP + from deployment.constants import ARTIFACTS_DIR from deployment.networks import is_local_network @@ -136,8 +137,7 @@ def _get_dependency_contract_container(contract: str) -> ContractContainer: return contract_container except AttributeError: continue - - raise ValueError(f"No contract found for {contract}") + raise ValueError(f"No contract found with name '{contract}'.") def get_contract_container(contract: str) -> ContractContainer: diff --git a/scripts/manage_subscription.py b/scripts/manage_subscription.py new file mode 100644 index 000000000..6ee1eb9e4 --- /dev/null +++ b/scripts/manage_subscription.py @@ -0,0 +1,174 @@ +import click +from ape import Contract +from ape.cli import account_option, ConnectedProviderCommand + +from deployment import registry +from deployment.options import ( + subscription_contract_option, + domain_option, + access_controller_option, + ritual_id_option, + encryptor_slots_option, + encryptors_option, +) +from deployment.params import Transactor +from deployment.utils import check_plugins + + +def _erc20_approve( + amount: int, + erc20: Contract, + receiver: Contract, + transactor: Transactor +) -> None: + """Approve an ERC20 transfer.""" + click.echo( + f"Approving transfer of {amount} {erc20.contract_type.name} " + f"to {receiver.contract_type.name}." + ) + transactor.transact( + erc20.approve, + receiver.address, + amount + ) + + +def _calculate_slot_fees( + subscription_contract: Contract, + slots: int +) -> int: + """Calculate the fees for a given number of encryptor slots.""" + duration = subscription_contract.subscriptionPeriodDuration() + encryptor_fees = subscription_contract.encryptorFees(slots, duration) + total_fees = encryptor_fees + return total_fees + + +@click.group() +def cli(): + """Subscription Management CLI""" + + +@cli.command(cls=ConnectedProviderCommand) +@account_option() +@domain_option +@subscription_contract_option +@encryptor_slots_option +@click.option( + "--period", + default=0, + help="Subscription billing period number to pay for.", +) +def pay_subscription(account, domain, subscription_contract, encryptor_slots, period): + """Pay for a new subscription period and initial encryptor slots.""" + check_plugins() + transactor = Transactor(account=account) + subscription_contract = registry.get_contract( + contract_name=subscription_contract, + domain=domain + ) + erc20 = Contract(subscription_contract.feeToken()) + base_fees = subscription_contract.baseFees(period) + slot_fees = _calculate_slot_fees( + subscription_contract=subscription_contract, + slots=encryptor_slots + ) + total_fees = base_fees + slot_fees + _erc20_approve( + amount=total_fees, + erc20=erc20, + receiver=subscription_contract, + transactor=transactor + ) + click.echo( + f"Paying for subscription period #{period} " + f"with {encryptor_slots} encryptor slots." + ) + transactor.transact( + subscription_contract.payForSubscription, + encryptor_slots + ) + + +@cli.command(cls=ConnectedProviderCommand) +@account_option() +@domain_option +@subscription_contract_option +@encryptor_slots_option +def pay_slots(account, domain, subscription_contract, encryptor_slots): + """Pay for additional encryptor slots in the current billing period.""" + check_plugins() + transactor = Transactor(account=account) + subscription_contract = registry.get_contract( + contract_name=subscription_contract, + domain=domain + ) + erc20 = Contract(subscription_contract.feeToken()) + fee = _calculate_slot_fees( + subscription_contract=subscription_contract, + slots=encryptor_slots + ) + _erc20_approve( + amount=fee, + erc20=erc20, + receiver=subscription_contract, + transactor=transactor + ) + click.echo(f"Paying for {encryptor_slots} new encryptor slots.") + transactor.transact( + subscription_contract.payForEncryptorSlots, + encryptor_slots + ) + + +@cli.command(cls=ConnectedProviderCommand) +@account_option() +@domain_option +@ritual_id_option +@access_controller_option +@encryptors_option +def add_encryptors(account, domain, ritual_id, access_controller, encryptors): + """Authorize encryptors to the access control contract for a ritual.""" + access_controller = registry.get_contract( + contract_name=access_controller, + domain=domain + ) + transactor = Transactor(account=account) + click.echo( + f"Adding {len(encryptors)} encryptors " + f"to the {access_controller} " + f"for ritual {ritual_id}." + ) + transactor.transact( + access_controller.authorize, + ritual_id, + encryptors + ) + + +@cli.command(cls=ConnectedProviderCommand) +@account_option() +@domain_option +@ritual_id_option +@access_controller_option +@encryptors_option +def remove_encryptors(account, domain, ritual_id, access_controller, encryptors): + """Deauthorize encryptors from the access control contract for a ritual.""" + transactor = Transactor(account=account) + access_controller = registry.get_contract( + contract_name=access_controller, + domain=domain + ) + click.echo( + f"Removing {len(encryptors)} " + f"encryptors to the {access_controller} " + f"for ritual {ritual_id}." + ) + transactor.transact( + access_controller.authorize, + ritual_id, encryptors + ) + + +if __name__ == "__main__": + cli() From 5caecf31efcc107e05b3e88690472204b88f283a Mon Sep 17 00:00:00 2001 From: KPrasch Date: Mon, 5 Aug 2024 15:49:04 +0700 Subject: [PATCH 093/105] fix typo in BqETHSubscription comment --- .../contracts/coordination/subscription/BqETHSubscription.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/contracts/coordination/subscription/BqETHSubscription.sol b/contracts/contracts/coordination/subscription/BqETHSubscription.sol index 5430cb8d7..46e304975 100644 --- a/contracts/contracts/coordination/subscription/BqETHSubscription.sol +++ b/contracts/contracts/coordination/subscription/BqETHSubscription.sol @@ -167,7 +167,7 @@ contract BqETHSubscription is EncryptorSlotsSubscription, Initializable, Ownable return baseFees(currentPeriodNumber); } - /// @dev optential overflow after 15-16 periods + /// @dev potential overflow after 15-16 periods function baseFees(uint256 periodNumber) public view returns (uint256) { uint256 baseFeeRate = initialBaseFeeRate * (INCREASE_BASE + baseFeeRateIncrease) ** periodNumber; From bf992ac349f375330ba02528fbe8e1878b356bed Mon Sep 17 00:00:00 2001 From: KPrasch Date: Wed, 7 Aug 2024 12:21:10 +0700 Subject: [PATCH 094/105] Apply suggestions from code review Co-authored-by: Derek Pierre --- scripts/manage_subscription.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/manage_subscription.py b/scripts/manage_subscription.py index 6ee1eb9e4..53815a1a0 100644 --- a/scripts/manage_subscription.py +++ b/scripts/manage_subscription.py @@ -161,7 +161,7 @@ def remove_encryptors(account, domain, ritual_id, access_controller, encryptors) ) click.echo( f"Removing {len(encryptors)} " - f"encryptors to the {access_controller} " + f"encryptors from the {access_controller} " f"for ritual {ritual_id}." ) transactor.transact( From f69c5dc87ce1b1c17ad309ad75adbc6a977569f6 Mon Sep 17 00:00:00 2001 From: KPrasch Date: Fri, 2 Aug 2024 21:23:17 +0700 Subject: [PATCH 095/105] Introduces node sampling utility function --- deployment/utils.py | 35 +++++++++++++++++++++++++++++++++-- scripts/initiate_ritual.py | 8 ++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/deployment/utils.py b/deployment/utils.py index 4fbfaaba6..be790c6a0 100644 --- a/deployment/utils.py +++ b/deployment/utils.py @@ -1,14 +1,15 @@ import json import os from pathlib import Path -from typing import Dict, List +from typing import Dict, List, Optional +import requests import yaml from ape import networks, project from ape.contracts import ContractContainer, ContractInstance from ape_etherscan.utils import API_KEY_ENV_KEY_MAP -from deployment.constants import ARTIFACTS_DIR +from deployment.constants import ARTIFACTS_DIR, MAINNET, LYNX, TAPIR from deployment.networks import is_local_network @@ -165,3 +166,33 @@ def get_chain_name(chain_id: int) -> str: if network.chain_id == chain_id: return f"{ecosystem_name} {network_name}" raise ValueError(f"Chain ID {chain_id} not found in networks.") + + +def sample_nodes( + domain: str, + num_nodes: int, + random_seed: Optional[int] = None, + duration: Optional[int] = None +): + porter_endpoints = { + MAINNET: "https://porter.nucypher.community/bucket_sampling", + LYNX: "https://porter-lynx.nucypher.network/get_ursulas", + TAPIR: "https://porter-tapir.nucypher.network/get_ursulas", + } + porter_endpoint = porter_endpoints.get(domain) + if not porter_endpoint: + raise ValueError(f"Porter endpoint not found for domain '{domain}'") + + params = { + "quantity": num_nodes, + } + if duration: + params["duration"] = duration + if domain == MAINNET and random_seed: + params["random_seed"] = random_seed + + response = requests.get(porter_endpoint, params=params) + data = response.json() + result = sorted(data["result"]["ursulas"], key=lambda x: x.lower()) + + return result diff --git a/scripts/initiate_ritual.py b/scripts/initiate_ritual.py index 64d23338a..dee0649e4 100644 --- a/scripts/initiate_ritual.py +++ b/scripts/initiate_ritual.py @@ -60,6 +60,14 @@ def cli(domain, duration, network, account, access_controller): authority = transactor.get_account().address while True: + + providers = sample_nodes( + domain=domain, + num_nodes=num_nodes, + duration=duration, + random_seed=random_seed + ) + transactor.transact( coordinator.initiateRitual, providers, authority, duration, access_controller.address ) From f153f28a6e194c754eaa1b0176f0055da697be78 Mon Sep 17 00:00:00 2001 From: KPrasch Date: Fri, 2 Aug 2024 21:24:16 +0700 Subject: [PATCH 096/105] implement new node sampling utility in initiate_ritual.py --- scripts/initiate_ritual.py | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/scripts/initiate_ritual.py b/scripts/initiate_ritual.py index dee0649e4..48e0fc044 100644 --- a/scripts/initiate_ritual.py +++ b/scripts/initiate_ritual.py @@ -4,10 +4,10 @@ from ape import project from ape.cli import ConnectedProviderCommand, account_option, network_option -from deployment.constants import LYNX, LYNX_NODES, SUPPORTED_TACO_DOMAINS, TAPIR, TAPIR_NODES +from deployment.constants import SUPPORTED_TACO_DOMAINS from deployment.params import Transactor from deployment.registry import contracts_from_registry -from deployment.utils import check_plugins, registry_filepath_from_domain +from deployment.utils import check_plugins, registry_filepath_from_domain, sample_nodes @click.command(cls=ConnectedProviderCommand) @@ -25,8 +25,7 @@ "-t", help="Duration of the ritual", type=int, - default=86400, - show_default=True, + required=True, ) @click.option( "--access-controller", @@ -35,21 +34,32 @@ type=click.Choice(["GlobalAllowList", "OpenAccessAuthorizer", "ManagedAllowList"]), required=True, ) -def cli(domain, duration, network, account, access_controller): +@click.option( + "--fee-model", + "-f", + help="The name of a fee model contract.", + type=click.Choice(["FreeFeeModel", "BqETHSubscription"]), + required=True, +) +@click.option( + "--num-nodes", + help="Number of nodes to use for the ritual.", + type=int, + required=True, +) +@click.option( + "--random-seed", + help="Random seed integer for sampling.", + required=False, + type=int +) +def cli(domain, duration, network, account, access_controller, fee_model, num_nodes, random_seed): check_plugins() print(f"Using network: {network}") print(f"Using domain: {domain}") print(f"Using account: {account}") transactor = Transactor(account=account) - if domain == LYNX: - providers = list(sorted(LYNX_NODES.keys())) - elif domain == TAPIR: - providers = list(sorted(TAPIR_NODES.keys())) - else: - # mainnet sampling not currently supported - raise ValueError(f"Sampling of providers not supported for domain '{domain}'") - registry_filepath = registry_filepath_from_domain(domain=domain) chain_id = project.chain_manager.chain_id From afa29c62ab02f3a2645ad70de2af88f1ed3bf18c Mon Sep 17 00:00:00 2001 From: KPrasch Date: Fri, 2 Aug 2024 21:45:12 +0700 Subject: [PATCH 097/105] basic error handle contract not found error in initiate_ritual.py --- scripts/initiate_ritual.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/initiate_ritual.py b/scripts/initiate_ritual.py index 48e0fc044..32d74e22c 100644 --- a/scripts/initiate_ritual.py +++ b/scripts/initiate_ritual.py @@ -66,7 +66,13 @@ def cli(domain, duration, network, account, access_controller, fee_model, num_no deployments = contracts_from_registry(filepath=registry_filepath, chain_id=chain_id) coordinator = deployments[project.Coordinator.contract_type.name] - access_controller = deployments[getattr(project, access_controller).contract_type.name] + # auxiliary contracts + try: + access_controller = deployments[getattr(project, access_controller).contract_type.name] + fee_model = deployments[getattr(project, fee_model).contract_type.name] + except KeyError as e: + raise ValueError(f"Contract not found in registry for domain {domain}: {e}") + authority = transactor.get_account().address while True: From 7d04896e0e07518806d47c47546cc4faa7bff9ba Mon Sep 17 00:00:00 2001 From: derekpierre Date: Fri, 2 Aug 2024 11:11:45 -0400 Subject: [PATCH 098/105] Add fee model contract address to initiate_ritual method parameters. Linter cleanup. --- scripts/initiate_ritual.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/scripts/initiate_ritual.py b/scripts/initiate_ritual.py index 32d74e22c..8639a2f94 100644 --- a/scripts/initiate_ritual.py +++ b/scripts/initiate_ritual.py @@ -47,12 +47,7 @@ type=int, required=True, ) -@click.option( - "--random-seed", - help="Random seed integer for sampling.", - required=False, - type=int -) +@click.option("--random-seed", help="Random seed integer for sampling.", required=False, type=int) def cli(domain, duration, network, account, access_controller, fee_model, num_nodes, random_seed): check_plugins() print(f"Using network: {network}") @@ -76,16 +71,17 @@ def cli(domain, duration, network, account, access_controller, fee_model, num_no authority = transactor.get_account().address while True: - providers = sample_nodes( - domain=domain, - num_nodes=num_nodes, - duration=duration, - random_seed=random_seed + domain=domain, num_nodes=num_nodes, duration=duration, random_seed=random_seed ) transactor.transact( - coordinator.initiateRitual, providers, authority, duration, access_controller.address + coordinator.initiateRitual, + fee_model.address, + providers, + authority, + duration, + access_controller.address, ) if not input("Another? [y/n] ").lower().startswith("y"): break From 351087941c5ca5b8086e54c8b1bd7a906ea3e1f7 Mon Sep 17 00:00:00 2001 From: KPrasch Date: Mon, 5 Aug 2024 16:41:02 +0700 Subject: [PATCH 099/105] parameterize ritual authoirity address --- scripts/initiate_ritual.py | 51 ++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/scripts/initiate_ritual.py b/scripts/initiate_ritual.py index 8639a2f94..f6e64b38f 100644 --- a/scripts/initiate_ritual.py +++ b/scripts/initiate_ritual.py @@ -11,7 +11,6 @@ @click.command(cls=ConnectedProviderCommand) -@network_option(required=True) @account_option() @click.option( "--domain", @@ -47,44 +46,48 @@ type=int, required=True, ) -@click.option("--random-seed", help="Random seed integer for sampling.", required=False, type=int) -def cli(domain, duration, network, account, access_controller, fee_model, num_nodes, random_seed): +@click.option( + "--random-seed", + help="Random seed integer for sampling.", + required=False, + type=int +) +@click.option( + "--authority", + help="The address of the ritual authority.", + required=False, + type=str +) +def cli(domain, duration, account, access_controller, fee_model, num_nodes, random_seed, authority): check_plugins() - print(f"Using network: {network}") - print(f"Using domain: {domain}") - print(f"Using account: {account}") transactor = Transactor(account=account) - registry_filepath = registry_filepath_from_domain(domain=domain) - chain_id = project.chain_manager.chain_id deployments = contracts_from_registry(filepath=registry_filepath, chain_id=chain_id) coordinator = deployments[project.Coordinator.contract_type.name] - # auxiliary contracts try: access_controller = deployments[getattr(project, access_controller).contract_type.name] fee_model = deployments[getattr(project, fee_model).contract_type.name] except KeyError as e: raise ValueError(f"Contract not found in registry for domain {domain}: {e}") - authority = transactor.get_account().address + if not authority: + authority = transactor.get_account().address + click.confirm(f"Using {authority} as the ritual authority. Continue?", abort=True) - while True: - providers = sample_nodes( - domain=domain, num_nodes=num_nodes, duration=duration, random_seed=random_seed - ) + providers = sample_nodes( + domain=domain, num_nodes=num_nodes, duration=duration, random_seed=random_seed + ) - transactor.transact( - coordinator.initiateRitual, - fee_model.address, - providers, - authority, - duration, - access_controller.address, - ) - if not input("Another? [y/n] ").lower().startswith("y"): - break + transactor.transact( + coordinator.initiateRitual, + fee_model.address, + providers, + authority, + duration, + access_controller.address, + ) if __name__ == "__main__": From 2717fa5b72ddef594f616ef07b9a876d61ec8c68 Mon Sep 17 00:00:00 2001 From: KPrasch Date: Tue, 6 Aug 2024 22:16:44 +0700 Subject: [PATCH 100/105] support for handpicked nodes file --- scripts/initiate_ritual.py | 85 +++++++++++++++++++++++++++++++------- 1 file changed, 69 insertions(+), 16 deletions(-) diff --git a/scripts/initiate_ritual.py b/scripts/initiate_ritual.py index f6e64b38f..c320b1173 100644 --- a/scripts/initiate_ritual.py +++ b/scripts/initiate_ritual.py @@ -2,7 +2,7 @@ import click from ape import project -from ape.cli import ConnectedProviderCommand, account_option, network_option +from ape.cli import ConnectedProviderCommand, account_option from deployment.constants import SUPPORTED_TACO_DOMAINS from deployment.params import Transactor @@ -10,6 +10,42 @@ from deployment.utils import check_plugins, registry_filepath_from_domain, sample_nodes +def validate_options(ctx): + if "handpicked" in ctx.params and ctx.params["handpicked"]: + if "num_nodes" in ctx.params and ctx.params["num_nodes"]: + raise click.BadOptionUsage( + option_name="--handpicked", + message="Cannot specify both --num-nodes and --handpicked.", + ) + if "random_seed" in ctx.params and ctx.params["random_seed"]: + raise click.BadOptionUsage( + option_name="--handpicked", + message="Cannot specify both --random-seed and --handpicked.", + ) + if not ctx.params.get("handpicked") and not ctx.params.get("num_nodes"): + raise click.BadOptionUsage( + option_name="--num-nodes", message="Must specify either --num-nodes or --handpicked." + ) + + +class MinInt(click.ParamType): + name = "minint" + + def __init__(self, min_value): + self.min_value = min_value + + def convert(self, value, param, ctx): + try: + ivalue = int(value) + except ValueError: + self.fail(f"{value} is not a valid integer", param, ctx) + if ivalue < self.min_value: + self.fail( + f"{value} is less than the minimum allowed value of {self.min_value}", param, ctx + ) + return ivalue + + @click.command(cls=ConnectedProviderCommand) @account_option() @click.option( @@ -22,14 +58,14 @@ @click.option( "--duration", "-t", - help="Duration of the ritual", - type=int, + help="Duration of the ritual in seconds. Must be at least 24h.", + type=MinInt(86400), required=True, ) @click.option( "--access-controller", "-a", - help="global allow list or open access authorizer.", + help="The name of an access controller contract.", type=click.Choice(["GlobalAllowList", "OpenAccessAuthorizer", "ManagedAllowList"]), required=True, ) @@ -44,21 +80,33 @@ "--num-nodes", help="Number of nodes to use for the ritual.", type=int, - required=True, + required=False, ) +@click.option("--random-seed", help="Random seed integer for sampling.", required=False, type=int) @click.option( - "--random-seed", - help="Random seed integer for sampling.", - required=False, - type=int + "--authority", help="The ethereum address of the ritual authority.", required=False, type=str ) @click.option( - "--authority", - help="The address of the ritual authority.", + "--handpicked", + help="The filepath of a file containing newline separated staking provider addresses.", required=False, - type=str + type=click.File("r"), ) -def cli(domain, duration, account, access_controller, fee_model, num_nodes, random_seed, authority): +def cli( + domain, + duration, + account, + access_controller, + fee_model, + num_nodes, + random_seed, + authority, + handpicked, +): + + ctx = click.get_current_context() + validate_options(ctx) + check_plugins() transactor = Transactor(account=account) registry_filepath = registry_filepath_from_domain(domain=domain) @@ -76,9 +124,14 @@ def cli(domain, duration, account, access_controller, fee_model, num_nodes, rand authority = transactor.get_account().address click.confirm(f"Using {authority} as the ritual authority. Continue?", abort=True) - providers = sample_nodes( - domain=domain, num_nodes=num_nodes, duration=duration, random_seed=random_seed - ) + if handpicked: + providers = sorted(line.lower() for line in handpicked) + if not providers: + raise ValueError(f"No staking providers found in the handpicked file {handpicked.name}") + else: + providers = sample_nodes( + domain=domain, num_nodes=num_nodes, duration=duration, random_seed=random_seed + ) transactor.transact( coordinator.initiateRitual, From 09e801f48aaf43bc17ed7dfed24a901d4850e02c Mon Sep 17 00:00:00 2001 From: KPrasch Date: Wed, 7 Aug 2024 11:54:40 +0700 Subject: [PATCH 101/105] Apply RFCs in PR #297 --- scripts/initiate_ritual.py | 51 +++++++++----------------------------- 1 file changed, 12 insertions(+), 39 deletions(-) diff --git a/scripts/initiate_ritual.py b/scripts/initiate_ritual.py index c320b1173..864e7857b 100644 --- a/scripts/initiate_ritual.py +++ b/scripts/initiate_ritual.py @@ -7,45 +7,10 @@ from deployment.constants import SUPPORTED_TACO_DOMAINS from deployment.params import Transactor from deployment.registry import contracts_from_registry +from deployment.types import MinInt from deployment.utils import check_plugins, registry_filepath_from_domain, sample_nodes -def validate_options(ctx): - if "handpicked" in ctx.params and ctx.params["handpicked"]: - if "num_nodes" in ctx.params and ctx.params["num_nodes"]: - raise click.BadOptionUsage( - option_name="--handpicked", - message="Cannot specify both --num-nodes and --handpicked.", - ) - if "random_seed" in ctx.params and ctx.params["random_seed"]: - raise click.BadOptionUsage( - option_name="--handpicked", - message="Cannot specify both --random-seed and --handpicked.", - ) - if not ctx.params.get("handpicked") and not ctx.params.get("num_nodes"): - raise click.BadOptionUsage( - option_name="--num-nodes", message="Must specify either --num-nodes or --handpicked." - ) - - -class MinInt(click.ParamType): - name = "minint" - - def __init__(self, min_value): - self.min_value = min_value - - def convert(self, value, param, ctx): - try: - ivalue = int(value) - except ValueError: - self.fail(f"{value} is not a valid integer", param, ctx) - if ivalue < self.min_value: - self.fail( - f"{value} is less than the minimum allowed value of {self.min_value}", param, ctx - ) - return ivalue - - @click.command(cls=ConnectedProviderCommand) @account_option() @click.option( @@ -104,10 +69,18 @@ def cli( handpicked, ): - ctx = click.get_current_context() - validate_options(ctx) - check_plugins() + if not (bool(handpicked) ^ (num_nodes is not None)): + raise click.BadOptionUsage( + option_name="--num-nodes", + message=f"Specify either --num-nodes or --handpicked; got {num_nodes} {handpicked}", + ) + if handpicked and random_seed: + raise click.BadOptionUsage( + option_name="--random-seed", + message="Cannot specify --random-seed when using --handpicked.", + ) + transactor = Transactor(account=account) registry_filepath = registry_filepath_from_domain(domain=domain) chain_id = project.chain_manager.chain_id From 0b0bbc7b796319fef148275a88cfe55c8944a10e Mon Sep 17 00:00:00 2001 From: KPrasch Date: Wed, 7 Aug 2024 11:59:15 +0700 Subject: [PATCH 102/105] Improves handling of porter sampling in initiate_ritual.py --- deployment/constants.py | 30 +++++++++++++++++++----------- deployment/types.py | 19 +++++++++++++++++++ deployment/utils.py | 20 +++++++------------- scripts/initiate_ritual.py | 1 - 4 files changed, 45 insertions(+), 25 deletions(-) create mode 100644 deployment/types.py diff --git a/deployment/constants.py b/deployment/constants.py index 92964597c..da69c52ea 100644 --- a/deployment/constants.py +++ b/deployment/constants.py @@ -4,10 +4,13 @@ import deployment +# +# Filesystem +# + DEPLOYMENT_DIR = Path(deployment.__file__).parent CONSTRUCTOR_PARAMS_DIR = DEPLOYMENT_DIR / "constructor_params" ARTIFACTS_DIR = DEPLOYMENT_DIR / "artifacts" -OZ_DEPENDENCY = project.dependencies["openzeppelin"]["5.0.0"] # # Domains @@ -20,7 +23,7 @@ SUPPORTED_TACO_DOMAINS = [LYNX, TAPIR, MAINNET] # -# Nodes +# Testnet # LYNX_NODES = { @@ -39,18 +42,23 @@ "0xcbE2F626d84c556AbA674FABBbBDdbED6B39d87b": "0xb057B982fB575509047e90cf5087c9B863a2022d", } -# EIP1967 +# +# Contracts +# + +OZ_DEPENDENCY = project.dependencies["openzeppelin"]["5.0.0"] -# Admin slot - https://eips.ethereum.org/EIPS/eip-1967#admin-address -EIP1967_ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103 +# EIP1967 Admin slot - https://eips.ethereum.org/EIPS/eip-1967#admin-address +EIP1967_ADMIN_SLOT = 0xB53127684A568B3173AE13B9F8A6016E243E63B6E8EE1178D6A717850B5D6103 +ACCESS_CONTROLLERS = ["GlobalAllowList", "OpenAccessAuthorizer", "ManagedAllowList"] # -# Contracts +# Sampling # -ACCESS_CONTROLLERS = [ - "GlobalAllowList", - "OpenAccessAuthorizer", - "ManagedAllowList" -] +PORTER_ENDPOINTS = { + MAINNET: "https://porter.nucypher.io/bucket_sampling", + LYNX: "https://porter-lynx.nucypher.io/get_ursulas", + TAPIR: "https://porter-tapir.nucypher.io/get_ursulas", +} diff --git a/deployment/types.py b/deployment/types.py new file mode 100644 index 000000000..02a3633bc --- /dev/null +++ b/deployment/types.py @@ -0,0 +1,19 @@ +import click + + +class MinInt(click.ParamType): + name = "minint" + + def __init__(self, min_value): + self.min_value = min_value + + def convert(self, value, param, ctx): + try: + ivalue = int(value) + except ValueError: + self.fail(f"{value} is not a valid integer", param, ctx) + if ivalue < self.min_value: + self.fail( + f"{value} is less than the minimum allowed value of {self.min_value}", param, ctx + ) + return ivalue diff --git a/deployment/utils.py b/deployment/utils.py index be790c6a0..af3f30c27 100644 --- a/deployment/utils.py +++ b/deployment/utils.py @@ -9,7 +9,7 @@ from ape.contracts import ContractContainer, ContractInstance from ape_etherscan.utils import API_KEY_ENV_KEY_MAP -from deployment.constants import ARTIFACTS_DIR, MAINNET, LYNX, TAPIR +from deployment.constants import ARTIFACTS_DIR, LYNX, MAINNET, PORTER_ENDPOINTS, TAPIR from deployment.networks import is_local_network @@ -49,7 +49,7 @@ def validate_config(config: Dict) -> Path: config_chain_id = deployment.get("chain_id") if not config_chain_id: raise ValueError("chain_id is not set in params file.") - + contracts = config.get("contracts") if not contracts: raise ValueError("Constructor parameters file missing 'contracts' field.") @@ -169,17 +169,9 @@ def get_chain_name(chain_id: int) -> str: def sample_nodes( - domain: str, - num_nodes: int, - random_seed: Optional[int] = None, - duration: Optional[int] = None + domain: str, num_nodes: int, random_seed: Optional[int] = None, duration: Optional[int] = None ): - porter_endpoints = { - MAINNET: "https://porter.nucypher.community/bucket_sampling", - LYNX: "https://porter-lynx.nucypher.network/get_ursulas", - TAPIR: "https://porter-tapir.nucypher.network/get_ursulas", - } - porter_endpoint = porter_endpoints.get(domain) + porter_endpoint = PORTER_ENDPOINTS.get(domain) if not porter_endpoint: raise ValueError(f"Porter endpoint not found for domain '{domain}'") @@ -188,7 +180,9 @@ def sample_nodes( } if duration: params["duration"] = duration - if domain == MAINNET and random_seed: + if random_seed: + if domain != MAINNET: + raise ValueError("'random_seed' is only a valid parameter for mainnet") params["random_seed"] = random_seed response = requests.get(porter_endpoint, params=params) diff --git a/scripts/initiate_ritual.py b/scripts/initiate_ritual.py index 864e7857b..2061793cc 100644 --- a/scripts/initiate_ritual.py +++ b/scripts/initiate_ritual.py @@ -68,7 +68,6 @@ def cli( authority, handpicked, ): - check_plugins() if not (bool(handpicked) ^ (num_nodes is not None)): raise click.BadOptionUsage( From f09774350c3eb205ae4033fade08a91032f45101 Mon Sep 17 00:00:00 2001 From: KPrasch Date: Wed, 7 Aug 2024 12:40:01 +0700 Subject: [PATCH 103/105] implement registry lookup utlities and tighten click types for ritual init CLI --- deployment/constants.py | 2 ++ deployment/types.py | 13 ++++++++ scripts/initiate_ritual.py | 64 ++++++++++++++++++++------------------ 3 files changed, 48 insertions(+), 31 deletions(-) diff --git a/deployment/constants.py b/deployment/constants.py index da69c52ea..6723c255d 100644 --- a/deployment/constants.py +++ b/deployment/constants.py @@ -53,6 +53,8 @@ ACCESS_CONTROLLERS = ["GlobalAllowList", "OpenAccessAuthorizer", "ManagedAllowList"] +FEE_MODELS = ["FreeFeeModel", "BqETHSubscription"] + # # Sampling # diff --git a/deployment/types.py b/deployment/types.py index 02a3633bc..cbdc61a2a 100644 --- a/deployment/types.py +++ b/deployment/types.py @@ -1,4 +1,5 @@ import click +from eth_utils import to_checksum_address class MinInt(click.ParamType): @@ -17,3 +18,15 @@ def convert(self, value, param, ctx): f"{value} is less than the minimum allowed value of {self.min_value}", param, ctx ) return ivalue + + +class ChecksumAddress(click.ParamType): + name = "checksum_address" + + def convert(self, value, param, ctx): + try: + value = to_checksum_address(value=value) + except ValueError: + self.fail("Invalid ethereum address") + else: + return value diff --git a/scripts/initiate_ritual.py b/scripts/initiate_ritual.py index 2061793cc..43abe0600 100644 --- a/scripts/initiate_ritual.py +++ b/scripts/initiate_ritual.py @@ -1,17 +1,16 @@ #!/usr/bin/python3 import click -from ape import project from ape.cli import ConnectedProviderCommand, account_option -from deployment.constants import SUPPORTED_TACO_DOMAINS +from deployment import registry +from deployment.constants import ACCESS_CONTROLLERS, FEE_MODELS, SUPPORTED_TACO_DOMAINS from deployment.params import Transactor -from deployment.registry import contracts_from_registry -from deployment.types import MinInt -from deployment.utils import check_plugins, registry_filepath_from_domain, sample_nodes +from deployment.types import ChecksumAddress, MinInt +from deployment.utils import check_plugins, sample_nodes -@click.command(cls=ConnectedProviderCommand) +@click.command(cls=ConnectedProviderCommand, name="initiate-ritual") @account_option() @click.option( "--domain", @@ -29,32 +28,40 @@ ) @click.option( "--access-controller", - "-a", - help="The name of an access controller contract.", - type=click.Choice(["GlobalAllowList", "OpenAccessAuthorizer", "ManagedAllowList"]), + "-c", + help="The registry name of an access controller contract.", + type=click.Choice(ACCESS_CONTROLLERS), required=True, ) @click.option( "--fee-model", "-f", help="The name of a fee model contract.", - type=click.Choice(["FreeFeeModel", "BqETHSubscription"]), + type=click.Choice(FEE_MODELS), + required=True, +) +@click.option( + "--authority", + "-a", + help="The ethereum address of the ritual authority.", required=True, + type=ChecksumAddress(), ) @click.option( "--num-nodes", + "-n", help="Number of nodes to use for the ritual.", type=int, - required=False, ) -@click.option("--random-seed", help="Random seed integer for sampling.", required=False, type=int) @click.option( - "--authority", help="The ethereum address of the ritual authority.", required=False, type=str + "--random-seed", + "-r", + help="Random seed integer for bucket sampling on mainnet.", + type=int, ) @click.option( "--handpicked", help="The filepath of a file containing newline separated staking provider addresses.", - required=False, type=click.File("r"), ) def cli( @@ -63,11 +70,14 @@ def cli( account, access_controller, fee_model, + authority, num_nodes, random_seed, - authority, handpicked, ): + """Initiate a ritual for a TACo domain.""" + + # Setup check_plugins() if not (bool(handpicked) ^ (num_nodes is not None)): raise click.BadOptionUsage( @@ -80,22 +90,7 @@ def cli( message="Cannot specify --random-seed when using --handpicked.", ) - transactor = Transactor(account=account) - registry_filepath = registry_filepath_from_domain(domain=domain) - chain_id = project.chain_manager.chain_id - deployments = contracts_from_registry(filepath=registry_filepath, chain_id=chain_id) - coordinator = deployments[project.Coordinator.contract_type.name] - - try: - access_controller = deployments[getattr(project, access_controller).contract_type.name] - fee_model = deployments[getattr(project, fee_model).contract_type.name] - except KeyError as e: - raise ValueError(f"Contract not found in registry for domain {domain}: {e}") - - if not authority: - authority = transactor.get_account().address - click.confirm(f"Using {authority} as the ritual authority. Continue?", abort=True) - + # Get the staking providers in the ritual cohort if handpicked: providers = sorted(line.lower() for line in handpicked) if not providers: @@ -105,6 +100,13 @@ def cli( domain=domain, num_nodes=num_nodes, duration=duration, random_seed=random_seed ) + # Get the contracts from the registry + coordinator = registry.get_contract(domain=domain, contract_name="Coordinator") + access_controller = registry.get_contract(domain=domain, contract_name=access_controller) + fee_model = registry.get_contract(domain=domain, contract_name=fee_model) + + # Initiate the ritual + transactor = Transactor(account=account) transactor.transact( coordinator.initiateRitual, fee_model.address, From 4ea90278709645f7d8d127ccded0710faec88ff8 Mon Sep 17 00:00:00 2001 From: KPrasch Date: Wed, 7 Aug 2024 13:14:17 +0700 Subject: [PATCH 104/105] require network option --- scripts/initiate_ritual.py | 7 +++++-- scripts/manage_subscription.py | 14 ++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/scripts/initiate_ritual.py b/scripts/initiate_ritual.py index 43abe0600..4f5c106c0 100644 --- a/scripts/initiate_ritual.py +++ b/scripts/initiate_ritual.py @@ -1,7 +1,7 @@ #!/usr/bin/python3 import click -from ape.cli import ConnectedProviderCommand, account_option +from ape.cli import ConnectedProviderCommand, account_option, network_option from deployment import registry from deployment.constants import ACCESS_CONTROLLERS, FEE_MODELS, SUPPORTED_TACO_DOMAINS @@ -12,6 +12,7 @@ @click.command(cls=ConnectedProviderCommand, name="initiate-ritual") @account_option() +@network_option(required=True) @click.option( "--domain", "-d", @@ -66,8 +67,9 @@ ) def cli( domain, - duration, account, + network, + duration, access_controller, fee_model, authority, @@ -79,6 +81,7 @@ def cli( # Setup check_plugins() + click.echo(f"Connected to {network.name} network.") if not (bool(handpicked) ^ (num_nodes is not None)): raise click.BadOptionUsage( option_name="--num-nodes", diff --git a/scripts/manage_subscription.py b/scripts/manage_subscription.py index 53815a1a0..48ada8de7 100644 --- a/scripts/manage_subscription.py +++ b/scripts/manage_subscription.py @@ -1,6 +1,6 @@ import click from ape import Contract -from ape.cli import account_option, ConnectedProviderCommand +from ape.cli import account_option, ConnectedProviderCommand, network_option from deployment import registry from deployment.options import ( @@ -51,6 +51,7 @@ def cli(): @cli.command(cls=ConnectedProviderCommand) @account_option() +@network_option(required=True) @domain_option @subscription_contract_option @encryptor_slots_option @@ -59,9 +60,10 @@ def cli(): default=0, help="Subscription billing period number to pay for.", ) -def pay_subscription(account, domain, subscription_contract, encryptor_slots, period): +def pay_subscription(account, network, domain, subscription_contract, encryptor_slots, period): """Pay for a new subscription period and initial encryptor slots.""" check_plugins() + click.echo(f"Connected to {network.name} network.") transactor = Transactor(account=account) subscription_contract = registry.get_contract( contract_name=subscription_contract, @@ -92,12 +94,14 @@ def pay_subscription(account, domain, subscription_contract, encryptor_slots, pe @cli.command(cls=ConnectedProviderCommand) @account_option() +@network_option(required=True) @domain_option @subscription_contract_option @encryptor_slots_option -def pay_slots(account, domain, subscription_contract, encryptor_slots): +def pay_slots(account, network, domain, subscription_contract, encryptor_slots): """Pay for additional encryptor slots in the current billing period.""" check_plugins() + click.echo(f"Connected to {network.name} network.") transactor = Transactor(account=account) subscription_contract = registry.get_contract( contract_name=subscription_contract, @@ -123,12 +127,14 @@ def pay_slots(account, domain, subscription_contract, encryptor_slots): @cli.command(cls=ConnectedProviderCommand) @account_option() +@network_option(required=True) @domain_option @ritual_id_option @access_controller_option @encryptors_option -def add_encryptors(account, domain, ritual_id, access_controller, encryptors): +def add_encryptors(account, network, domain, ritual_id, access_controller, encryptors): """Authorize encryptors to the access control contract for a ritual.""" + click.echo(f"Connected to {network.name} network.") access_controller = registry.get_contract( contract_name=access_controller, domain=domain From 05b960ee5bde0c9ed963d2bf44ac467e47019581 Mon Sep 17 00:00:00 2001 From: KPrasch Date: Wed, 7 Aug 2024 19:53:29 +0700 Subject: [PATCH 105/105] respond to RFCs in PR #297 --- deployment/constants.py | 2 +- deployment/utils.py | 4 ++-- scripts/initiate_ritual.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deployment/constants.py b/deployment/constants.py index 6723c255d..cb8808cfa 100644 --- a/deployment/constants.py +++ b/deployment/constants.py @@ -59,7 +59,7 @@ # Sampling # -PORTER_ENDPOINTS = { +PORTER_SAMPLING_ENDPOINTS = { MAINNET: "https://porter.nucypher.io/bucket_sampling", LYNX: "https://porter-lynx.nucypher.io/get_ursulas", TAPIR: "https://porter-tapir.nucypher.io/get_ursulas", diff --git a/deployment/utils.py b/deployment/utils.py index af3f30c27..0d731be1d 100644 --- a/deployment/utils.py +++ b/deployment/utils.py @@ -9,7 +9,7 @@ from ape.contracts import ContractContainer, ContractInstance from ape_etherscan.utils import API_KEY_ENV_KEY_MAP -from deployment.constants import ARTIFACTS_DIR, LYNX, MAINNET, PORTER_ENDPOINTS, TAPIR +from deployment.constants import ARTIFACTS_DIR, LYNX, MAINNET, PORTER_SAMPLING_ENDPOINTS, TAPIR from deployment.networks import is_local_network @@ -171,7 +171,7 @@ def get_chain_name(chain_id: int) -> str: def sample_nodes( domain: str, num_nodes: int, random_seed: Optional[int] = None, duration: Optional[int] = None ): - porter_endpoint = PORTER_ENDPOINTS.get(domain) + porter_endpoint = PORTER_SAMPLING_ENDPOINTS.get(domain) if not porter_endpoint: raise ValueError(f"Porter endpoint not found for domain '{domain}'") diff --git a/scripts/initiate_ritual.py b/scripts/initiate_ritual.py index 4f5c106c0..9dd80e654 100644 --- a/scripts/initiate_ritual.py +++ b/scripts/initiate_ritual.py @@ -85,7 +85,7 @@ def cli( if not (bool(handpicked) ^ (num_nodes is not None)): raise click.BadOptionUsage( option_name="--num-nodes", - message=f"Specify either --num-nodes or --handpicked; got {num_nodes} {handpicked}", + message=f"Specify either --num-nodes or --handpicked; got {num_nodes}, {handpicked}.", ) if handpicked and random_seed: raise click.BadOptionUsage(