Skip to content

Commit

Permalink
registry: implement stateless property
Browse files Browse the repository at this point in the history
  • Loading branch information
facuspagnuolo committed Jun 21, 2023
1 parent c9bf227 commit 3e562fe
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 58 deletions.
16 changes: 10 additions & 6 deletions packages/deployer/contracts/Deployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,12 @@ contract Deployer {

/**
* @dev Task params
* @param custom Whether the implementation is custom or not, if it is it won't be checked with Mimic's Registry
* @param impl Address of the task implementation to be used
* @param initializeData Call-data to initialize the new task instance
*/
struct TaskParams {
bool custom;
address impl;
bytes initializeData;
}
Expand All @@ -102,7 +104,7 @@ contract Deployer {
* @dev Deploys a new authorizer instance
*/
function deployAuthorizer(string memory namespace, string memory name, AuthorizerParams memory params) external {
address instance = _deployClone(namespace, name, params.impl);
address instance = _deployClone(namespace, name, params.impl, true);
Authorizer(instance).initialize(params.owners);
emit AuthorizerDeployed(namespace, name, instance, params.impl);
}
Expand All @@ -111,7 +113,7 @@ contract Deployer {
* @dev Deploys a new smart vault instance
*/
function deploySmartVault(string memory namespace, string memory name, SmartVaultParams memory params) external {
address payable instance = payable(_deployClone(namespace, name, params.impl));
address payable instance = payable(_deployClone(namespace, name, params.impl, true));
SmartVault(instance).initialize(params.authorizer, params.priceOracle, params.priceFeedParams);
emit SmartVaultDeployed(namespace, name, instance, params.impl);
}
Expand All @@ -120,20 +122,22 @@ contract Deployer {
* @dev Deploys a new task instance
*/
function deployTask(string memory namespace, string memory name, TaskParams memory params) external {
address instance = _deployClone(namespace, name, params.impl);
address instance = _deployClone(namespace, name, params.impl, !params.custom);
if (params.initializeData.length > 0) instance.functionCall(params.initializeData, 'DEPLOYER_TASK_INIT_FAILED');
emit TaskDeployed(namespace, name, instance, params.impl);
}

/**
* @dev Deploys a new clone using CREATE3
*/
function _deployClone(string memory namespace, string memory name, address implementation)
function _deployClone(string memory namespace, string memory name, address implementation, bool check)
internal
returns (address)
{
require(registry.isRegistered(implementation), 'DEPLOYER_IMPL_NOT_REGISTERED');
require(!registry.isDeprecated(implementation), 'DEPLOYER_IMPL_DEPRECATED');
if (check) {
require(registry.isRegistered(implementation), 'DEPLOYER_IMPL_NOT_REGISTERED');
require(!registry.isDeprecated(implementation), 'DEPLOYER_IMPL_DEPRECATED');
}

bytes memory bytecode = abi.encodePacked(
hex'3d602d80600a3d3981f3363d3d373d3d3d363d73',
Expand Down
12 changes: 6 additions & 6 deletions packages/deployer/test/Deployer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe('Deployer', () => {

context('when the implementation is registered', () => {
beforeEach('register implementation', async () => {
await registry.connect(mimic).register('[email protected]', authorizer.address)
await registry.connect(mimic).register('[email protected]', authorizer.address, false)
})

context('when the implementation is not deprecated', () => {
Expand Down Expand Up @@ -172,8 +172,8 @@ describe('Deployer', () => {

context('when the implementation is registered', () => {
beforeEach('register implementations', async () => {
await registry.connect(mimic).register('[email protected]', smartVault.address)
await registry.connect(mimic).register('[email protected]', PRICE_ORACLE)
await registry.connect(mimic).register('[email protected]', smartVault.address, false)
await registry.connect(mimic).register('[email protected]', PRICE_ORACLE, true)
})

context('when the implementation is not deprecated', () => {
Expand Down Expand Up @@ -292,10 +292,10 @@ describe('Deployer', () => {
const FEE_CONTROLLER = '0x0000000000000000000000000000000000000001'
const WNT = '0x0000000000000000000000000000000000000002'
const smartVaultImpl = await deploy(ARTIFACTS.SMART_VAULT, [registry.address, FEE_CONTROLLER, WNT])
await registry.connect(mimic).register('[email protected]', smartVaultImpl.address)
await registry.connect(mimic).register('[email protected]', smartVaultImpl.address, false)

const PRICE_ORACLE = '0x0000000000000000000000000000000000000004'
await registry.connect(mimic).register('[email protected]', PRICE_ORACLE)
await registry.connect(mimic).register('[email protected]', PRICE_ORACLE, true)

const tx = await deployer.deploySmartVault(namespace, 'smart-vault', {
impl: smartVaultImpl.address,
Expand All @@ -316,7 +316,7 @@ describe('Deployer', () => {

context('when the implementation is registered', () => {
beforeEach('register implementations', async () => {
await registry.connect(mimic).register('[email protected]', task.address)
await registry.connect(mimic).register('[email protected]', task.address, false)
})

context('when the implementation is not deprecated', () => {
Expand Down
19 changes: 13 additions & 6 deletions packages/registry/contracts/Registry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ contract Registry is IRegistry, Ownable {
// List of registered implementations
mapping (address => bool) public override isRegistered;

// List of stateless implementations
mapping (address => bool) public override isStateless;

// List of deprecated implementations
mapping (address => bool) public override isDeprecated;

Expand All @@ -42,19 +45,21 @@ contract Registry is IRegistry, Ownable {
* @dev Creates and registers an implementation
* @param name Name of the implementation
* @param code Code of the implementation to create and register
* @param stateless Whether the new implementation is considered stateless or not
*/
function create(string memory name, bytes memory code) external override onlyOwner {
function create(string memory name, bytes memory code, bool stateless) external override onlyOwner {
address implementation = CREATE3.deploy(keccak256(abi.encode(name)), code, 0);
_register(name, implementation);
_register(name, implementation, stateless);
}

/**
* @dev Registers an implementation
* @param name Name logged for the implementation
* @param implementation Address of the implementation to be registered
* @param stateless Whether the given implementation is considered stateless or not
*/
function register(string memory name, address implementation) external override onlyOwner {
_register(name, implementation);
function register(string memory name, address implementation, bool stateless) external override onlyOwner {
_register(name, implementation, stateless);
}

/**
Expand All @@ -74,12 +79,14 @@ contract Registry is IRegistry, Ownable {
* @dev Registers an implementation
* @param name Name of the implementation
* @param implementation Address of the implementation to be registered
* @param stateless Whether the given implementation is considered stateless or not
*/
function _register(string memory name, address implementation) internal {
function _register(string memory name, address implementation, bool stateless) internal {
require(implementation != address(0), 'REGISTRY_IMPL_ADDRESS_ZERO');
require(!isRegistered[implementation], 'REGISTRY_IMPL_ALREADY_REGISTERED');

isRegistered[implementation] = true;
emit Registered(implementation, name);
isStateless[implementation] = stateless;
emit Registered(implementation, name, stateless);
}
}
14 changes: 11 additions & 3 deletions packages/registry/contracts/interfaces/IRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ interface IRegistry {
/**
* @dev Emitted every time an implementation is registered
*/
event Registered(address indexed implementation, string name);
event Registered(address indexed implementation, string name, bool stateless);

/**
* @dev Emitted every time an implementation is deprecated
Expand All @@ -38,6 +38,12 @@ interface IRegistry {
*/
function isRegistered(address implementation) external view returns (bool);

/**
* @dev Tells whether an implementation is stateless or not
* @param implementation Address of the implementation being queried
*/
function isStateless(address implementation) external view returns (bool);

/**
* @dev Tells whether an implementation is deprecated
* @param implementation Address of the implementation being queried
Expand All @@ -48,15 +54,17 @@ interface IRegistry {
* @dev Creates and registers an implementation
* @param name Name of the implementation
* @param code Code of the implementation to create and register
* @param stateless Whether the new implementation is considered stateless or not
*/
function create(string memory name, bytes memory code) external;
function create(string memory name, bytes memory code, bool stateless) external;

/**
* @dev Registers an implementation
* @param name Name of the implementation
* @param implementation Address of the implementation to be registered
* @param stateless Whether the given implementation is considered stateless or not
*/
function register(string memory name, address implementation) external;
function register(string memory name, address implementation, bool stateless) external;

/**
* @dev Deprecates an implementation
Expand Down
86 changes: 64 additions & 22 deletions packages/registry/test/Registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,35 +44,57 @@ describe('Registry', () => {

context('when the requested implementation was not created', () => {
context('when the requested implementation is not registered', () => {
it('creates and registers the requested implementation', async () => {
const tx = await registry.create(name, bytecode)
context('when the requested implementation is considered stateless', () => {
const stateless = true

const event = await assertEvent(tx, 'Registered', { name })
it('creates and registers the requested implementation', async () => {
const tx = await registry.create(name, bytecode, stateless)

const implementation = event.args.implementation
expect(await registry.isRegistered(implementation)).to.be.true
expect(await registry.isDeprecated(implementation)).to.be.false
const event = await assertEvent(tx, 'Registered', { name, stateless })

const implementation = event.args.implementation
expect(await registry.isRegistered(implementation)).to.be.true
expect(await registry.isStateless(implementation)).to.be.true
expect(await registry.isDeprecated(implementation)).to.be.false
})
})

context('when the requested implementation is not considered stateless', () => {
const stateless = false

it('creates and registers the requested implementation', async () => {
const tx = await registry.create(name, bytecode, stateless)

const event = await assertEvent(tx, 'Registered', { name, stateless })

const implementation = event.args.implementation
expect(await registry.isRegistered(implementation)).to.be.true
expect(await registry.isStateless(implementation)).to.be.false
expect(await registry.isDeprecated(implementation)).to.be.false
})
})
})

context('when the requested implementation is registered', () => {
beforeEach('register', async () => {
await registry.register(name, implementation)
await registry.register(name, implementation, true)
})

it('reverts', async () => {
await expect(registry.register(name, implementation)).to.be.revertedWith('REGISTRY_IMPL_ALREADY_REGISTERED')
await expect(registry.register(name, implementation, true)).to.be.revertedWith(
'REGISTRY_IMPL_ALREADY_REGISTERED'
)
})
})
})

context('when the requested implementation was created', () => {
beforeEach('create', async () => {
await registry.create(name, bytecode)
await registry.create(name, bytecode, true)
})

it('reverts', async () => {
await expect(registry.create(name, bytecode)).to.be.revertedWith('DEPLOYMENT_FAILED')
await expect(registry.create(name, bytecode, true)).to.be.revertedWith('DEPLOYMENT_FAILED')
})
})
})
Expand All @@ -83,7 +105,7 @@ describe('Registry', () => {
})

it('reverts', async () => {
await expect(registry.create(name, implementation)).to.be.revertedWith('Ownable: caller is not the owner')
await expect(registry.create(name, implementation, true)).to.be.revertedWith('Ownable: caller is not the owner')
})
})
})
Expand All @@ -97,27 +119,44 @@ describe('Registry', () => {
})

context('when the requested implementation is not registered', () => {
it('registers the requested implementation', async () => {
await registry.register(name, implementation)
context('when the requested implementation is considered stateless', () => {
const stateless = true

it('registers the requested implementation', async () => {
const tx = await registry.register(name, implementation, stateless)

expect(await registry.isRegistered(implementation)).to.be.true
expect(await registry.isDeprecated(implementation)).to.be.false
await assertEvent(tx, 'Registered', { name, implementation, stateless })

expect(await registry.isRegistered(implementation)).to.be.true
expect(await registry.isStateless(implementation)).to.be.true
expect(await registry.isDeprecated(implementation)).to.be.false
})
})

it('emits an event', async () => {
const tx = await registry.register(name, implementation)
context('when the requested implementation is not considered stateless', () => {
const stateless = false

it('registers the requested implementation', async () => {
const tx = await registry.register(name, implementation, stateless)

await assertEvent(tx, 'Registered', { name, implementation, stateless })

await assertEvent(tx, 'Registered', { name, implementation })
expect(await registry.isRegistered(implementation)).to.be.true
expect(await registry.isStateless(implementation)).to.be.false
expect(await registry.isDeprecated(implementation)).to.be.false
})
})
})

context('when the requested implementation is registered', () => {
beforeEach('register', async () => {
await registry.register(name, implementation)
await registry.register(name, implementation, true)
})

it('reverts', async () => {
await expect(registry.register(name, implementation)).to.be.revertedWith('REGISTRY_IMPL_ALREADY_REGISTERED')
await expect(registry.register(name, implementation, true)).to.be.revertedWith(
'REGISTRY_IMPL_ALREADY_REGISTERED'
)
})
})
})
Expand All @@ -128,7 +167,9 @@ describe('Registry', () => {
})

it('reverts', async () => {
await expect(registry.register(name, implementation)).to.be.revertedWith('Ownable: caller is not the owner')
await expect(registry.register(name, implementation, true)).to.be.revertedWith(
'Ownable: caller is not the owner'
)
})
})
})
Expand All @@ -147,14 +188,15 @@ describe('Registry', () => {

context('when the requested implementation is registered', () => {
beforeEach('register', async () => {
await registry.register('[email protected]', implementation)
await registry.register('[email protected]', implementation, true)
})

context('when the requested implementation is not deprecated', () => {
it('registers the requested implementation', async () => {
await registry.deprecate(implementation)

expect(await registry.isRegistered(implementation)).to.be.true
expect(await registry.isStateless(implementation)).to.be.true
expect(await registry.isDeprecated(implementation)).to.be.true
})

Expand Down
8 changes: 5 additions & 3 deletions packages/smart-vault/contracts/SmartVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ contract SmartVault is ISmartVault, Authorized, ReentrancyGuardUpgradeable {
authP(authParams(connector))
returns (bytes memory result)
{
_validateDependency(connector);
_validateDependency(connector, true);
result = Address.functionDelegateCall(connector, data, 'SMART_VAULT_EXECUTE_FAILED');
emit Executed(connector, data, result);
}
Expand Down Expand Up @@ -260,7 +260,7 @@ contract SmartVault is ISmartVault, Authorized, ReentrancyGuardUpgradeable {
*/
function _setPriceOracle(address newPriceOracle) internal {
require(newPriceOracle != address(0), 'SMART_VAULT_ORACLE_ZERO');
_validateDependency(newPriceOracle);
_validateDependency(newPriceOracle, true);
priceOracle = newPriceOracle;
emit PriceOracleSet(newPriceOracle);
}
Expand All @@ -279,10 +279,12 @@ contract SmartVault is ISmartVault, Authorized, ReentrancyGuardUpgradeable {
/**
* @dev Validates a dependency against the Mimic Registry
* @param dependency Address of the dependency to validate
* @param stateless Whether the given dependency must be stateless or not
*/
function _validateDependency(address dependency) private view {
function _validateDependency(address dependency, bool stateless) private view {
if (isDependencyCheckIgnored[dependency]) return;
require(IRegistry(registry).isRegistered(dependency), 'SMART_VAULT_DEP_NOT_REGISTERED');
require(IRegistry(registry).isStateless(dependency) == stateless, 'SMART_VAULT_DEP_BAD_STATE_COND');
require(!IRegistry(registry).isDeprecated(dependency), 'SMART_VAULT_DEP_DEPRECATED');
}
}
Loading

0 comments on commit 3e562fe

Please sign in to comment.