Modifier invocations only work when the modifier is an identifier. A modifier m defined in library L can only be referred to as the member access L.m. There's no way to use it.
Would a modifier placed in a library act as an internal library function and thus be inlined or would be called remotely via delegatecall?
I was thinking of them as internal.
Modifiers in libraries are really useful thing. I was working on the state machine library, and function modifiers are good companions to it allowing aspect-oriented applications.
library StateMachine {
enum State {
// Since `destroyself()` zeroes values of all variables, we need the first state (corresponding to zero)
// to indicate that contract had being destroyed
Destroyed,
Offline,
InsufficientStake,
UnderPenalty,
Idle,
Computing
}
struct StateMachine {
bool initialized;
State currentState;
mapping(uint8 => State[]) transitionTable;
}
modifier transitionToState(
StateMachine storage _machine,
State _newState
) {
// Should not happen
assert(_machine.currentState == State.Destroyed);
// Checking if the state transition is allowed
State[] storage allowedStates = _machine.transitionTable[uint8(_machine.currentState)];
for (uint no = 0; no < allowedStates.length; no++) {
if (allowedStates[no] == _newState) {
_;
_machine.currentState = _newState;
return;
}
}
revert();
}
modifier transitionThroughState(
StateMachine storage _machine,
State _transitionState
) {
// Should not happen
assert(_machine.currentState == State.Destroyed);
// Checking if the state transitions are allowed
bool firstTransitionAllowed = false;
State[] storage allowedStates = _machine.transitionTable[uint8(_machine.currentState)];
for (uint no = 0; no < allowedStates.length; no++) {
if (allowedStates[no] == _transitionState) {
firstTransitionAllowed = true;
break;
}
}
require(firstTransitionAllowed == true);
bool secondTransitionAllowed = false;
allowedStates = _machine.transitionTable[uint8(_transitionState)];
for (no = 0; no < allowedStates.length; no++) {
if (allowedStates[no] == _machine.currentState) {
secondTransitionAllowed = true;
break;
}
}
require(secondTransitionAllowed == true);
State initialState = _machine.currentState;
_machine.currentState = _transitionState;
_;
_machine.currentState = initialState;
}
modifier requireState(
StateMachine storage _machine,
State _requiredState
) {
require(_machine.currentState == _requiredState);
_;
}
function initStateMachine(
StateMachine storage _machine
) internal {
require(_machine.initialized == false);
_machine.currentState = State.Offline;
_machine.transitionTable[uint8(State.Offline)] = [ State.InsufficientStake, State.Idle ];
/*
and so on ...
*/
_machine.initialized = true;
}
}
contract StateMachineUser {
using StateMachine for StateMachine.StateMachine;
StateMachine.StateMachine internal stateMachine;
function currentState() public returns (StateMachine.State) {
return stateMachine.currentState;
}
function StateMachineUser () {
stateMachine.initStateMachine();
}
function someFunction(
// ...
) StateMachine.requireState(StateMachine.State.Idle)
StateMachine.transitionToState(StateMachine.State.Computing) {
// some code ...
}
}
This useful solution is not working for now since there is no way to import modifiers from a library.
I would like to work on this.
@axic @frangio
Let's take an overall look. From documentation:
Libraries are similar to contracts, but their purpose is that they are deployed only once at a specific address and their code is reused using the DELEGATECALL
There is no way to use modifiers from libraries directly because there is no such objects in EVM. Of course we can translate them to some kind of library functions and then emulate modifiers substitution, but this is not good as different approach and behaviour introduced for "normal modifiers" and "library modifiers". So inlining is the only/best option.
Now let's look at the libraries in Solidity. Why not to inline everything? Why we need DELEGATECALL at all? I understand that some gas saving during deployment can be reached, but is it enough? Maybe it is time to introduce a new concept? Here is the first sight proposal:
externalLibrary MathLib (0x8812...ed0f);. Source code can also be provided as a reference for function calls. Or current library declaration can be extended with address addition. Could you provide a clear point of view on a way how to resolve this issue?
I'm ready to code when you make a decision about implementation approach.
@limexp thanks! Modifiers work like internal functions, they are just inlined.
The first step of this PR is actually enhancing the parser to support fully qualified names in function definitions, i.e. it doesn't accept the dot currently in:
function f() Library.modifier() {}
@axic
For modifiers there is no difference if they are defined in a library or in another contract.
As I understand the function f1() definition must be correct. But what about function f2()?
pragma solidity ^0.4.11;
library libraryName {
modifier libraryModifier() { _; }
}
contract contractName {
modifier contractModifier() { _; }
}
contract test {
modifier localModifier() { _; }
// function f1() libraryName.libraryModifier { }
// function f2() contractName.contractModifier { }
function f3() localModifier { }
}
I need a small guidance where is a right place to add tests for this issue?
@axic
There are several ways this issue could be resolved. I am new to this project, so please help to select the best approach according to spirit of ethereum/solidity.
class ModifierInvocation definition, but change Identifier semantics, so it could contain period https://github.com/ethereum/solidity/blob/81f9f86ce51d2e9b54bf76b1169f12e193c79745/libsolidity/ast/AST.h#L756 ASTPointer<Identifier> m_modifierLibraryName; attribute into class ModifierInvocation. m_modifierName attribute create a new ModifierName type like Identifier, but with "period" semantics.m_modifierName attribute create a new ModifierName type like UserDefinedTypeName.m_modifierName to UserDefinedTypeName type.P.S. I personally prefer the most general approach nr.4, or the minimum code change nr.1.
I just ran into this myself. Kind of shocked they aren't already added into the functionality of the language. Modifiers in libraries definitely enable reusable safety features and I really think this should be added in.
@limexp i don't mind option 2.
Be aware that modifiers are polymorphic. If you reference a modifier through a base class name, it should be exactly that modifier. If you reference it through a library, this should be the same.
Probably it is best to change Identifier to something new like NamePath and then also use NamePath inside UserDefinedTypeName.
This is basically like 3 but a little more generic.
Comment to show support for this features, especially since it might help contracts that run out of gas during deployment (i.e. - by splitting out code to different contracts).
@roschler sorry to disappoint you, but I don't see a way how to call modifiers defined in libraries using delegatecall.
Most helpful comment
@axic
There are several ways this issue could be resolved. I am new to this project, so please help to select the best approach according to spirit of ethereum/solidity.
class ModifierInvocationdefinition, but changeIdentifiersemantics, so it could contain period https://github.com/ethereum/solidity/blob/81f9f86ce51d2e9b54bf76b1169f12e193c79745/libsolidity/ast/AST.h#L756ASTPointer<Identifier> m_modifierLibraryName;attribute intoclass ModifierInvocation.m_modifierNameattribute create a newModifierNametype likeIdentifier, but with "period" semantics.m_modifierNameattribute create a newModifierNametype likeUserDefinedTypeName.m_modifierNametoUserDefinedTypeNametype.P.S. I personally prefer the most general approach nr.4, or the minimum code change nr.1.