Repository: vyperlang/vyper

Issue:
VIP: Convert Event construction to use kwargs
## Simple Summary
Make the syntax for creating event instances more clear, readable, and consistent with the syntax for struct creation by leveraging keyword arguments.

## Motivation
Structs have been updated to be constructed by kwargs ( #2381 ). This new syntax makes struct construction more convenient than the previous implementation.

Event creation could benefit from this same syntax change, not only improving the existing event syntax, but also making it consistent with struct creation, which lowers the mental overhead programmers need to work with when writing Vyper.

Right now, users have to remember the two different, but similar syntaxes for creating events and for creating structs. The syntax for creating structs is more readable, clear, and explicit. So we should update events to use the same syntax as structs.

## Specification
There are no semantic changes involved in this VIP.

### Curent Syntax
The current syntax around events uses positional arguments as follows
```vyper
# event definition
event EntryCreated:
    id: indexed(uint256)
    round_id: indexed(uint256)
    owner: indexed(address)
    amount: uint256

# event construction
log EntryCreated(foo, bar, msg.sender, 1000)
```
### Proposed Syntax
We propose changing this to the following syntax which uses keyword arguments
```vyper
# event definition (unchanged)
event EntryCreated:
    id: indexed(uint256)
    round_id: indexed(uint256)
    owner: indexed(address)
    amount: uint256

# event construction
log EntryCreated(id=foo, round_id=bar, owner=msg.sender, amount=1000)
```

### Comparison with Struct Syntax
The proposed kwarg is more consistent with the current syntax around structs, and the benefits of the change are very apparent when seen side by side

Given the following struct and event definitions:

```vyper
# Struct Definition
struct Entry:
    id: uint256
    round_id: uint256
    owner: address
    amount: uint256

# Event Definition (unchanged)
event EntryCreated:
    id: indexed(uint256)
    round_id: indexed(uint256)
    owner: indexed(address)
    amount: uint256
```

We construct these as follows:

**Existing Syntax**
```vyper
# Creating a Struct
entry: Entry = Entry(id=foo, round_id=bar, owner=msg.sender, amount=10000)
# Logging an Event
log EntryCreated(foo, bar, msg.sender, 10000)
```

**Proposed Syntax**
```vyper
# Creating a Struct
entry: Entry = Entry(id=foo, round_id=bar, owner=msg.sender, amount=10000)
# Logging an Event
log EntryCreated(id=foo, round_id=bar, owner=msg.sender, amount=10000)
```

## Backwards Compatibility
Similarly to the Struct Kwarg VIP, we can support both syntax options via an AST transformation and warn the user about the change. After a few releases, the previous syntax can be dropped, as it is better to have *one* syntax for this in the long run.

## Dependencies
N/A

## References
#2381 
#3777 

## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/)


Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/vyperlang/vyper/pull/4257?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) Report
Attention: Patch coverage is `94.28571%` with `2 lines` in your changes missing coverage. Please review.
> Project coverage is 88.34%. Comparing base [(`48a5da4`)](https://app.codecov.io/gh/vyperlang/vyper/commit/48a5da46b7c553d29181880e8ff6d51da62b2679?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) to head [(`4a4df37`)](https://app.codecov.io/gh/vyperlang/vyper/commit/4a4df37bd4b89d96b0d72d63a36ee8d8b3564f3e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang).

| [Files with missing lines](https://app.codecov.io/gh/vyperlang/vyper/pull/4257?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) | Patch % | Lines |
|---|---|---|
| [vyper/semantics/types/user.py](https://app.codecov.io/gh/vyperlang/vyper/pull/4257?src=pr&el=tree&filepath=vyper%2Fsemantics%2Ftypes%2Fuser.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvc2VtYW50aWNzL3R5cGVzL3VzZXIucHk=) | 93.54% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/vyperlang/vyper/pull/4257?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #4257      +/-   ##
==========================================
- Coverage   91.35%   88.34%   -3.02%     
==========================================
  Files         109      109              
  Lines       15636    15663      +27     
  Branches     3436     3442       +6     
==========================================
- Hits        14285    13837     -448     
- Misses        921     1298     +377     
- Partials      430      528      +98     
```

| [Flag](https://app.codecov.io/gh/vyperlang/vyper/pull/4257/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) | Coverage Δ | |
|---|---|---|
| [](https://app.codecov.io/gh/vyperlang/vyper/pull/4257/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) | `88.34% <94.28%> (-2.91%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/vyperlang/vyper/pull/4257?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang).

z80dev: merging in master dropped the coverage a good amount but from looking at the report I don't think its related to this PR, but will look into it
charles-cooper: > merging in master dropped the coverage a good amount but from looking at the report I don't think its related to this PR, but will look into it

i wouldn't worry about it too much, the codecov app often fails to merge the coverage files properly

Patch:
diff --git a/vyper/codegen/stmt.py b/vyper/codegen/stmt.py
index 225cede7..b1e26d7d 100644
--- a/vyper/codegen/stmt.py
+++ b/vyper/codegen/stmt.py
@@ -93,7 +93,13 @@ class Stmt:
     def parse_Log(self):
         event = self.stmt._metadata["type"]
 
-        args = [Expr(arg, self.context).ir_node for arg in self.stmt.value.args]
+        if len(self.stmt.value.keywords) > 0:
+            # keyword arguments
+            to_compile = [arg.value for arg in self.stmt.value.keywords]
+        else:
+            # positional arguments
+            to_compile = self.stmt.value.args
+        args = [Expr(arg, self.context).ir_node for arg in to_compile]
 
         topic_ir = []
         data_ir = []
diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py
index b5292b1d..809c6532 100644
--- a/vyper/semantics/analysis/local.py
+++ b/vyper/semantics/analysis/local.py
@@ -810,13 +810,17 @@ class ExprVisitor(VyperNodeVisitorBase):
                 self.visit(kwarg.value, typ)
 
         elif is_type_t(func_type, EventT):
-            # events have no kwargs
+            # event ctors
             expected_types = func_type.typedef.arguments.values()  # type: ignore
-            for arg, typ in zip(node.args, expected_types):
-                self.visit(arg, typ)
+            # Handle keyword args if present, otherwise use positional args
+            if len(node.keywords) > 0:
+                for kwarg, arg_type in zip(node.keywords, expected_types):
+                    self.visit(kwarg.value, arg_type)
+            else:
+                for arg, typ in zip(node.args, expected_types):
+                    self.visit(arg, typ)
         elif is_type_t(func_type, StructT):
             # struct ctors
-            # ctors have no kwargs
             expected_types = func_type.typedef.members.values()  # type: ignore
             for kwarg, arg_type in zip(node.keywords, expected_types):
                 self.visit(kwarg.value, arg_type)
diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py
index 90bb631e..6816fbed 100644
--- a/vyper/semantics/analysis/module.py
+++ b/vyper/semantics/analysis/module.py
@@ -897,7 +897,7 @@ def _import_to_path(level: int, module_str: str) -> PurePath:
         base_path = "../" * (level - 1)
     elif level == 1:
         base_path = "./"
-    return PurePath(f"{base_path}{module_str.replace('.','/')}/")
+    return PurePath(f"{base_path}{module_str.replace('.', '/')}/")
 
 
 # can add more, e.g. "vyper.builtins.interfaces", etc.
diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py
index d30eee79..9734087f 100644
--- a/vyper/semantics/analysis/utils.py
+++ b/vyper/semantics/analysis/utils.py
@@ -1,9 +1,11 @@
 import itertools
-from typing import Callable, Iterable, List
+from typing import Any, Callable, Iterable, List
 
 from vyper import ast as vy_ast
 from vyper.exceptions import (
     CompilerPanic,
+    InstantiationException,
+    InvalidAttribute,
     InvalidLiteral,
     InvalidOperation,
     InvalidReference,
@@ -694,3 +696,43 @@ def get_expr_writes(node: vy_ast.VyperNode) -> OrderedSet[VarAccess]:
         ret |= get_expr_writes(c)
     node._metadata["writes_r"] = ret
     return ret
+
+
+def validate_kwargs(node: vy_ast.Call, members: dict[str, VyperType], typeclass: str):
+    # manually validate kwargs for better error messages instead of
+    # relying on `validate_call_args`
+
+    seen: dict[str, vy_ast.keyword] = {}
+    membernames = list(members.keys())
+
+    # check duplicate kwargs
+    for i, kwarg in enumerate(node.keywords):
+        # x=5 => kwarg(arg="x", value=Int(5))
+        argname = kwarg.arg
+        if argname in seen:
+            prev = seen[argname]
+            raise InvalidAttribute(f"Duplicate {typeclass} argument", prev, kwarg)
+        seen[argname] = kwarg
+
+        hint: Any  # mypy kludge
+        if argname not in members:
+            hint = get_levenshtein_error_suggestions(argname, members, 1.0)
+            raise UnknownAttribute(f"Unknown {typeclass} argument.", kwarg, hint=hint)
+
+        expect_name = membernames[i]
+        if argname != expect_name:
+            # out of order key
+            msg = f"{typeclass} keys are required to be in order, but got"
+            msg += f" `{argname}` instead of `{expect_name}`."
+            hint = "as a reminder, the order of the keys in this"
+            hint += f" {typeclass} are {list(members)}"
+            raise InvalidAttribute(msg, kwarg, hint=hint)
+
+        expected_type = members[argname]
+        validate_expected_type(kwarg.value, expected_type)
+
+    missing = OrderedSet(members.keys()) - OrderedSet(seen.keys())
+    if len(missing) > 0:
+        msg = f"{typeclass} instantiation missing fields:"
+        msg += f" {', '.join(list(missing))}"
+        raise InstantiationException(msg, node)
diff --git a/vyper/semantics/types/user.py b/vyper/semantics/types/user.py
index ca8e99bc..73fa4878 100644
--- a/vyper/semantics/types/user.py
+++ b/vyper/semantics/types/user.py
@@ -7,21 +7,23 @@ from vyper.ast.validation import validate_call_args
 from vyper.exceptions import (
     EventDeclarationException,
     FlagDeclarationException,
-    InvalidAttribute,
+    InstantiationException,
     NamespaceCollision,
     StructureException,
     UnfoldableNode,
-    UnknownAttribute,
     VariableDeclarationException,
 )
 from vyper.semantics.analysis.base import Modifiability
-from vyper.semantics.analysis.levenshtein_utils import get_levenshtein_error_suggestions
-from vyper.semantics.analysis.utils import check_modifiability, validate_expected_type
+from vyper.semantics.analysis.utils import (
+    check_modifiability,
+    validate_expected_type,
+    validate_kwargs,
+)
 from vyper.semantics.data_locations import DataLocation
 from vyper.semantics.types.base import VyperType
 from vyper.semantics.types.subscriptable import HashMapT
 from vyper.semantics.types.utils import type_from_abi, type_from_annotation
-from vyper.utils import keccak256
+from vyper.utils import keccak256, vyper_warn
 
 
 # user defined type
@@ -281,6 +283,25 @@ class EventT(_UserType):
         return cls(base_node.name, members, indexed, base_node)
 
     def _ctor_call_return(self, node: vy_ast.Call) -> None:
+        # validate keyword arguments if provided
+        if len(node.keywords) > 0:
+            if len(node.args) > 0:
+                raise InstantiationException(
+                    "Event instantiation requires either all keyword arguments "
+                    "or all positional arguments",
+                    node,
+                )
+
+            return validate_kwargs(node, self.arguments, self.typeclass)
+
+        # warn about positional argument depreciation
+        msg = "Instantiating events with positional arguments is "
+        msg += "deprecated as of v0.4.1 and will be disallowed "
+        msg += "in a future release. Use kwargs instead eg. "
+        msg += "Foo(a=1, b=2)"
+
+        vyper_warn(msg, node)
+
         validate_call_args(node, len(self.arguments))
         for arg, expected in zip(node.args, self.arguments.values()):
             validate_expected_type(arg, expected)
@@ -415,31 +436,7 @@ class StructT(_UserType):
                 "Struct contains a mapping and so cannot be declared as a literal", node
             )
 
-        # manually validate kwargs for better error messages instead of
-        # relying on `validate_call_args`
-        members = self.member_types.copy()
-        keys = list(self.member_types.keys())
-        for i, kwarg in enumerate(node.keywords):
-            # x=5 => kwarg(arg="x", value=Int(5))
-            argname = kwarg.arg
-            if argname not in members:
-                hint = get_levenshtein_error_suggestions(argname, members, 1.0)
-                raise UnknownAttribute("Unknown or duplicate struct member.", kwarg, hint=hint)
-            expected = keys[i]
-            if argname != expected:
-                raise InvalidAttribute(
-                    "Struct keys are required to be in order, but got "
-                    f"`{argname}` instead of `{expected}`. (Reminder: the "
-                    f"keys in this struct are {list(self.member_types.items())})",
-                    kwarg,
-                )
-            expected_type = members.pop(argname)
-            validate_expected_type(kwarg.value, expected_type)
-
-        if members:
-            raise VariableDeclarationException(
-                f"Struct declaration does not define all fields: {', '.join(list(members))}", node
-            )
+        validate_kwargs(node, self.member_types, self.typeclass)
 
         return self
 
diff --git a/vyper/utils.py b/vyper/utils.py
index 5307cd11..d635c783 100644
--- a/vyper/utils.py
+++ b/vyper/utils.py
@@ -89,6 +89,7 @@ class OrderedSet(Generic[_T]):
     def union(self, other):
         return self | other
 
+    # set dunders
     def __ior__(self, other):
         self.update(other)
         return self
@@ -101,6 +102,15 @@ class OrderedSet(Generic[_T]):
     def __eq__(self, other):
         return self._data == other._data
 
+    def __isub__(self, other):
+        self.dropmany(other)
+        return self
+
+    def __sub__(self, other):
+        ret = self.copy()
+        ret.dropmany(other)
+        return ret
+
     def copy(self):
         cls = self.__class__
         ret = cls.__new__(cls)


Test patch:
diff --git a/tests/functional/builtins/codegen/test_empty.py b/tests/functional/builtins/codegen/test_empty.py
index dd6c5c7c..30881622 100644
--- a/tests/functional/builtins/codegen/test_empty.py
+++ b/tests/functional/builtins/codegen/test_empty.py
@@ -672,11 +672,11 @@ event MyLog:
 @external
 def foo():
     log MyLog(
-        b'hellohellohellohellohellohellohellohellohello',
-        empty(int128[2][3]),
-        314159,
-        b'helphelphelphelphelphelphelphelphelphelphelp',
-        empty(uint256[3])
+        arg1=b'hellohellohellohellohellohellohellohellohello',
+        arg2=empty(int128[2][3]),
+        arg3=314159,
+        arg4=b'helphelphelphelphelphelphelphelphelphelphelp',
+        arg5=empty(uint256[3])
     )
     """
 
diff --git a/tests/functional/codegen/calling_convention/test_default_function.py b/tests/functional/codegen/calling_convention/test_default_function.py
index 4d54e31f..08d9c086 100644
--- a/tests/functional/codegen/calling_convention/test_default_function.py
+++ b/tests/functional/codegen/calling_convention/test_default_function.py
@@ -28,7 +28,7 @@ event Sent:
 @external
 @payable
 def __default__():
-    log Sent(msg.sender)
+    log Sent(sender=msg.sender)
     """
     c = get_contract(code)
     env.set_balance(env.deployer, 10**18)
@@ -46,13 +46,13 @@ event Sent:
 @external
 @payable
 def fooBar(a: int128 = 12345) -> int128:
-    log Sent(empty(address))
+    log Sent(sender=empty(address))
     return a
 
 @external
 @payable
 def __default__():
-    log Sent(msg.sender)
+    log Sent(sender=msg.sender)
     """
     c = get_contract(code)
     env.set_balance(env.deployer, 10**18)
@@ -69,7 +69,7 @@ event Sent:
 
 @external
 def __default__():
-    log Sent(msg.sender)
+    log Sent(sender=msg.sender)
     """
     c = get_contract(code)
     env.set_balance(env.deployer, 10**17)
@@ -103,7 +103,7 @@ event Sent:
     sender: indexed(address)
 
 def __default__():
-    log Sent(msg.sender)
+    log Sent(sender=msg.sender)
     """
     assert_compile_failed(lambda: get_contract(code))
 
@@ -119,12 +119,12 @@ event Sent:
 @payable
 # function selector: 0x00000000
 def blockHashAskewLimitary(v: uint256) -> uint256:
-    log Sent(2)
+    log Sent(sig=2)
     return 7
 
 @external
 def __default__():
-    log Sent(1)
+    log Sent(sig=1)
     """
     c = get_contract(code)
 
@@ -165,12 +165,12 @@ event Sent:
 @payable
 # function selector: 0x00000000
 def wycpnbqcyf() -> uint256:
-    log Sent(2)
+    log Sent(sig=2)
     return 7
 
 @external
 def __default__():
-    log Sent(1)
+    log Sent(sig=1)
     """
     c = get_contract(code)
 
@@ -205,12 +205,12 @@ event Sent:
 @payable
 # function selector: 0xd88e0b00
 def fow() -> uint256:
-    log Sent(2)
+    log Sent(sig=2)
     return 7
 
 @external
 def __default__():
-    log Sent(1)
+    log Sent(sig=1)
     """
     c = get_contract(code)
 
diff --git a/tests/functional/codegen/features/decorators/test_private.py b/tests/functional/codegen/features/decorators/test_private.py
index d313aa3b..b9e34ea4 100644
--- a/tests/functional/codegen/features/decorators/test_private.py
+++ b/tests/functional/codegen/features/decorators/test_private.py
@@ -436,7 +436,7 @@ def i_am_me() -> bool:
 @external
 @nonpayable
 def whoami() -> address:
-    log Addr(self._whoami())
+    log Addr(addr=self._whoami())
     return self._whoami()
     """
 
diff --git a/tests/functional/codegen/features/test_logging.py b/tests/functional/codegen/features/test_logging.py
index cf77a30b..2bb646e6 100644
--- a/tests/functional/codegen/features/test_logging.py
+++ b/tests/functional/codegen/features/test_logging.py
@@ -5,13 +5,14 @@ from eth_utils import to_text
 from tests.utils import decimal_to_int
 from vyper import compile_code
 from vyper.exceptions import (
-    ArgumentException,
     EventDeclarationException,
+    InstantiationException,
     InvalidType,
     NamespaceCollision,
     StructureException,
     TypeMismatch,
     UndeclaredDefinition,
+    UnknownAttribute,
 )
 from vyper.utils import keccak256
 
@@ -50,7 +51,7 @@ event MyLog:
 @external
 def foo():
     self.a = b"bar"
-    log MyLog(self.a)
+    log MyLog(arg1=self.a)
     """
 
     c = get_contract(loggy_code)
@@ -78,7 +79,7 @@ event MyLog:
 
 @external
 def foo():
-    log MyLog(-2, True, self)
+    log MyLog(arg1=-2, arg2=True, arg3=self)
     """
 
     c = get_contract(loggy_code)
@@ -120,7 +121,7 @@ b: address
 def foo(arg1: int128):
     a: bool = True
     self.b = self
-    log MyLog(arg1, a, self.b)
+    log MyLog(arg1=arg1, arg2=a, arg3=self.b)
     """
 
     c = get_contract(code)
@@ -141,13 +142,13 @@ event MyLog:
 
 @external
 def foo():
-    log MyLog(1, self)
-    log MyLog(1, self)
+    log MyLog(arg1=1, arg2=self)
+    log MyLog(arg1=1, arg2=self)
 
 @external
 def bar():
-    log MyLog(1, self)
-    log MyLog(1, self)
+    log MyLog(arg1=1, arg2=self)
+    log MyLog(arg1=1, arg2=self)
     """
 
     c = get_contract(loggy_code)
@@ -198,7 +199,7 @@ event MyLog:
 
 @external
 def foo():
-    log MyLog(123)
+    log MyLog(arg1=123)
     """
 
     c = get_contract(loggy_code)
@@ -231,8 +232,16 @@ event MyLog:
 
 @external
 def foo():
-    log MyLog([1,2], [block.timestamp, block.timestamp+1, block.timestamp+2], [[1,2],[1,2]])
-    log MyLog([1,2], [block.timestamp, block.timestamp+1, block.timestamp+2], [[1,2],[1,2]])
+    log MyLog(
+        arg1=[1,2],
+        arg2=[block.timestamp, block.timestamp+1, block.timestamp+2],
+        arg3=[[1,2],[1,2]]
+    )
+    log MyLog(
+        arg1=[1,2],
+        arg2=[block.timestamp, block.timestamp+1, block.timestamp+2],
+        arg3=[[1,2],[1,2]]
+    )
     """
 
     c = get_contract(loggy_code)
@@ -271,7 +280,7 @@ event MyLog:
 
 @external
 def foo(arg1: Bytes[29], arg2: Bytes[31]):
-    log MyLog(b'bar', arg1, arg2)
+    log MyLog(arg1=b'bar', arg2=arg1, arg3=arg2)
 """
 
     c = get_contract(loggy_code)
@@ -307,7 +316,7 @@ event MyLog:
 
 @external
 def foo(_arg1: Bytes[20]):
-    log MyLog(_arg1)
+    log MyLog(arg1=_arg1)
     """
 
     c = get_contract(loggy_code)
@@ -335,7 +344,7 @@ event MyLog:
 
 @external
 def foo(_arg1: Bytes[5]):
-    log MyLog(_arg1)
+    log MyLog(arg1=_arg1)
     """
 
     c = get_contract(loggy_code)
@@ -369,7 +378,7 @@ event MyLog:
 
 @external
 def foo():
-    log MyLog(123, b'home', b'bar', 0xc305c901078781C232A2a521C2aF7980f8385ee9, self, block.timestamp)  # noqa: E501
+    log MyLog(arg1=123, arg2=b'home', arg3=b'bar', arg4=0xc305c901078781C232A2a521C2aF7980f8385ee9, arg5=self, arg6=block.timestamp)  # noqa: E501
     """
 
     c = get_contract(loggy_code)
@@ -412,7 +421,7 @@ event MyLog:
 
 @external
 def foo():
-    log MyLog(1, b'bar')
+    log MyLog(arg1=1, arg2=b'bar')
     """
 
     c = get_contract(loggy_code)
@@ -457,8 +466,8 @@ event YourLog:
 
 @external
 def foo():
-    log MyLog(1, b'bar')
-    log YourLog(self, MyStruct(x=1, y=b'abc', z=SmallStruct(t='house', w=13.5)))
+    log MyLog(arg1=1, arg2=b'bar')
+    log YourLog(arg1=self, arg2=MyStruct(x=1, y=b'abc', z=SmallStruct(t='house', w=13.5)))
     """
 
     c = get_contract(loggy_code)
@@ -524,7 +533,7 @@ event MyLog:
 
 @external
 def foo_():
-    log MyLog(b'yo')
+    log MyLog(arg1=b'yo')
 """
 
     with tx_failed(TypeMismatch):
@@ -539,7 +548,7 @@ event MyLog:
 
 @external
 def foo():
-    log MyLog(b'bars')
+    log MyLog(arg1=b'bars')
 """
 
     with tx_failed(TypeMismatch):
@@ -553,7 +562,7 @@ event MyLog:
 
 @external
 def foo(arg1: Bytes[4]):
-    log MyLog(arg1)
+    log MyLog(arg1=arg1)
 """
 
     with tx_failed(TypeMismatch):
@@ -567,7 +576,7 @@ event MyLog:
 
 @external
 def foo():
-    log MyLog(b'bars')
+    log MyLog(arg1=b'bars')
 """
 
     with tx_failed(TypeMismatch):
@@ -581,7 +590,7 @@ event MyLog:
 
 @external
 def foo(arg1: Bytes[4]):
-    log MyLog(arg1)
+    log MyLog(arg1=arg1)
 """
 
     with tx_failed(TypeMismatch):
@@ -610,7 +619,7 @@ event MyLog:
 
 @deploy
 def __init__():
-    log MyLog(1, 2, 3, 4)
+    log MyLog(arg1=1, arg2=2, arg3=3, arg4=4)
     """
 
     with tx_failed(EventDeclarationException):
@@ -650,7 +659,7 @@ event MyLog:
 
 @external
 def foo():
-    log MyLog(self)
+    log MyLog(arg1=self)
     """
 
     with tx_failed(TypeMismatch):
@@ -664,7 +673,7 @@ event MyLog:
 
 @external
 def foo():
-    log MyLog(self)
+    log MyLog(arg1=self)
     """
 
     with tx_failed(TypeMismatch):
@@ -680,9 +689,9 @@ event MyLog:
 
 @external
 def foo():
-    log MyLog(1, 2)
+    log MyLog(arg1=1, arg2=2)
 """
-    with tx_failed(ArgumentException):
+    with tx_failed(UnknownAttribute):
         get_contract(loggy_code)
 
 
@@ -694,9 +703,9 @@ event MyLog:
 
 @external
 def foo():
-    log MyLog(1)
+    log MyLog(arg1=1)
 """
-    with tx_failed(ArgumentException):
+    with tx_failed(InstantiationException):
         get_contract(loggy_code)
 
 
@@ -852,7 +861,7 @@ event Bar:
 @external
 def foo():
     a: int128[4] = [1, 2, 3, 4]
-    log Bar(a)
+    log Bar(_value=a)
     """
     c = get_contract(code)
 
@@ -868,7 +877,7 @@ event Bar:
 
 @external
 def foo():
-    log Bar([1, 2, 3, 4])
+    log Bar(_value=[1, 2, 3, 4])
     """
     c = get_contract(code)
 
@@ -886,7 +895,7 @@ x: int128[4]
 
 @external
 def foo():
-    log Bar(self.x)
+    log Bar(_value=self.x)
 
 @external
 def set_list():
@@ -910,7 +919,7 @@ event Bar:
 
 @external
 def foo(barbaric: int128[4]):
-    log Bar(barbaric)
+    log Bar(_value=barbaric)
     """
     c = get_contract(code)
 
@@ -926,7 +935,7 @@ event Bar:
 
 @external
 def foo():
-    log Bar([1.11, 2.22, 3.33, 4.44])
+    log Bar(_value=[1.11, 2.22, 3.33, 4.44])
     """
     c = get_contract(code)
 
@@ -949,7 +958,7 @@ x:Bytes[5]
 
 @external
 def foo(a: int128):
-    log MyLog(self.x)
+    log MyLog(arg1=self.x)
 
 @external
 def setbytez():
@@ -975,7 +984,7 @@ x: decimal[4]
 
 @external
 def foo():
-    log Bar(self.x)
+    log Bar(_value=self.x)
 
 @external
 def set_list():
@@ -1004,7 +1013,7 @@ event Bar:
 
 @external
 def foo(inp: Bytes[33]):
-    log Bar(inp)
+    log Bar(_value=inp)
 """
     with tx_failed(TypeMismatch):
         get_contract(code)
@@ -1019,7 +1028,7 @@ event Bar:
 @external
 def foo():
     a: int128[4] = [1, 2, 3, 4]
-    log Bar(10, a)
+    log Bar(arg1=10, arg2=a)
     """
     c = get_contract(code)
 
@@ -1037,7 +1046,7 @@ x: int128[4]
 
 @external
 def foo():
-    log Bar(10, self.x)
+    log Bar(arg1=10, arg2=self.x)
 
 @external
 def set_list():
@@ -1071,7 +1080,7 @@ def __init__():
 @external
 def foo():
     v: int128[3] = [7, 8, 9]
-    log Bar(10, self.x, b"test", v, self.y)
+    log Bar(arg1=10, arg2=self.x, arg3=b"test", arg4=v, arg5=self.y)
 
 @external
 def set_list():
@@ -1104,7 +1113,7 @@ event MyLog:
 
 @external
 def foo(a: Bytes[36], b: int128, c: String[7]):
-    log MyLog(a, b, c)
+    log MyLog(arg1=a, arg2=b, arg3=c)
     """
 
     c = get_contract(loggy_code)
@@ -1144,7 +1153,7 @@ def foo():
     a: Bytes[10] = b"potato"
     b: int128 = -777
     c: String[44] = "why hello, neighbor! how are you today?"
-    log MyLog(a, b, c)
+    log MyLog(arg1=a, arg2=b, arg3=c)
     """
 
     c = get_contract(loggy_code)
@@ -1191,7 +1200,7 @@ def setter(_a: Bytes[32], _b: int128, _c: String[6]):
 
 @external
 def foo():
-    log MyLog(self.a, self.b, self.c)
+    log MyLog(arg1=self.a, arg2=self.b, arg3=self.c)
     """
 
     c = get_contract(loggy_code)
@@ -1229,7 +1238,7 @@ event MyLog:
 
 @external
 def foo():
-    log MyLog(b"wow", 666, "madness!")
+    log MyLog(arg1=b"wow", arg2=666, arg3="madness!")
     """
 
     c = get_contract(loggy_code)
diff --git a/tests/functional/codegen/features/test_logging_bytes_extended.py b/tests/functional/codegen/features/test_logging_bytes_extended.py
index 6b84cdd2..64c848bb 100644
--- a/tests/functional/codegen/features/test_logging_bytes_extended.py
+++ b/tests/functional/codegen/features/test_logging_bytes_extended.py
@@ -7,7 +7,7 @@ event MyLog:
 
 @external
 def foo():
-    log MyLog(667788, b'hellohellohellohellohellohellohellohellohello', 334455)
+    log MyLog(arg1=667788, arg2=b'hellohellohellohellohellohellohellohellohello', arg3=334455)
     """
 
     c = get_contract(code)
@@ -31,7 +31,7 @@ def foo():
     a: Bytes[64] = b'hellohellohellohellohellohellohellohellohello'
     b: Bytes[64] = b'hellohellohellohellohellohellohellohello'
     # test literal much smaller than buffer
-    log MyLog(a, b, b'hello')
+    log MyLog(arg1=a, arg2=b, arg3=b'hello')
     """
 
     c = get_contract(code)
@@ -51,7 +51,7 @@ event MyLog:
 
 @external
 def foo(a: int128, b: Bytes[64], c: int128):
-    log MyLog(a, b, c)
+    log MyLog(arg1=a, arg2=b, arg3=c)
     """
 
     c = get_contract(code)
@@ -77,7 +77,7 @@ c: int128
 
 @external
 def foo():
-    log MyLog(self.a, self.b, self.c)
+    log MyLog(arg1=self.a, arg2=self.b, arg3=self.c)
 
 @external
 def set(x: int128, y: Bytes[64], z: int128):
@@ -114,10 +114,10 @@ event MyLog:
 @external
 def foo():
     log MyLog(
-        [[24, 26], [12, 10]],
-        b'hellohellohellohellohellohellohellohellohello',
-        314159,
-        b'helphelphelphelphelphelphelphelphelphelphelp'
+        arg1=[[24, 26], [12, 10]],
+        arg2=b'hellohellohellohellohellohellohellohellohello',
+        arg3=314159,
+        arg4=b'helphelphelphelphelphelphelphelphelphelphelp'
     )
     """
 
diff --git a/tests/functional/codegen/features/test_logging_from_call.py b/tests/functional/codegen/features/test_logging_from_call.py
index 190be7b4..2b14cd83 100644
--- a/tests/functional/codegen/features/test_logging_from_call.py
+++ b/tests/functional/codegen/features/test_logging_from_call.py
@@ -21,11 +21,11 @@ def to_bytes32(_value: uint256) -> bytes32:
 @external
 def test_func(_value: uint256):
     data2: Bytes[60] = concat(self.to_bytes32(_value),self.to_bytes(_value),b"testing")
-    log TestLog(self.to_bytes32(_value), data2, self.to_bytes(_value))
+    log TestLog(testData1=self.to_bytes32(_value), testData2=data2, testData3=self.to_bytes(_value))
 
     loggedValue: bytes32 = self.to_bytes32(_value)
     loggedValue2: Bytes[8] = self.to_bytes(_value)
-    log TestLog(loggedValue, data2, loggedValue2)
+    log TestLog(testData1=loggedValue, testData2=data2, testData3=loggedValue2)
     """
 
     c = get_contract(code)
@@ -65,8 +65,8 @@ def test_func(_value: uint256,input: Bytes[133]):
 
     data2: Bytes[200] = b"hello world"
 
-    # log TestLog(self.to_bytes32(_value),input,self.to_bytes(_value))
-    log TestLog(self.to_bytes32(_value),input,"bababa")
+    # log TestLog(testData1=self.to_bytes32(_value),testData2=input,testData3=self.to_bytes(_value))
+    log TestLog(testData1=self.to_bytes32(_value),testData2=input,testData3="bababa")
     """
 
     c = get_contract(code)
@@ -99,8 +99,8 @@ def test_func(_value: uint256,input: Bytes[133]):
     data2: Bytes[200] = b"hello world"
 
     # log will be malformed
-    # log TestLog(self.to_bytes32(_value),input,self.to_bytes(_value))
-    log TestLog(self.to_bytes32(_value), input)
+    # log TestLog(testData1=self.to_bytes32(_value),testData2=input,testData3=self.to_bytes(_value))
+    log TestLog(testData1=self.to_bytes32(_value), testData2=input)
     """
 
     c = get_contract(code)
@@ -137,12 +137,12 @@ def test_func(_value: uint256,input: Bytes[2048]):
     data2: Bytes[2064] = concat(self.to_bytes(_value),self.to_bytes(_value),input)
 
     # log will be malformed
-    log TestLog(self.to_bytes32(_value), data2, self.to_bytes(_value))
+    log TestLog(testData1=self.to_bytes32(_value), testData2=data2, testData3=self.to_bytes(_value))
 
     loggedValue: Bytes[8] = self.to_bytes(_value)
 
     # log will be normal
-    log TestLog(self.to_bytes32(_value),data2,loggedValue)
+    log TestLog(testData1=self.to_bytes32(_value),testData2=data2,testData3=loggedValue)
     """
 
     c = get_contract(code)
diff --git a/tests/functional/codegen/features/test_memory_dealloc.py b/tests/functional/codegen/features/test_memory_dealloc.py
index 3be57038..b733de73 100644
--- a/tests/functional/codegen/features/test_memory_dealloc.py
+++ b/tests/functional/codegen/features/test_memory_dealloc.py
@@ -9,7 +9,7 @@ interface Other:
 
 @external
 def foo(target: address) -> uint256[2]:
-    log Shimmy(empty(address), 3)
+    log Shimmy(a=empty(address), b=3)
     amount: uint256 = 1
     flargen: uint256 = 42
     extcall Other(target).sendit()
diff --git a/tests/functional/codegen/modules/test_events.py b/tests/functional/codegen/modules/test_events.py
index ae5198cf..c32a66ca 100644
--- a/tests/functional/codegen/modules/test_events.py
+++ b/tests/functional/codegen/modules/test_events.py
@@ -50,7 +50,7 @@ event MyEvent:
 
 @internal
 def foo():
-    log MyEvent(5, 6)
+    log MyEvent(x=5, y=6)
     """
     main = """
 import lib1
diff --git a/tests/functional/codegen/types/test_string.py b/tests/functional/codegen/types/test_string.py
index 51899b50..1c186eeb 100644
--- a/tests/functional/codegen/types/test_string.py
+++ b/tests/functional/codegen/types/test_string.py
@@ -116,7 +116,7 @@ event MyLog:
 
 @external
 def foo():
-    log MyLog(667788, 'hellohellohellohellohellohellohellohellohello', 334455)
+    log MyLog(arg1=667788, arg2='hellohellohellohellohellohellohellohellohello', arg3=334455)
     """
 
     c = get_contract(code)
diff --git a/tests/functional/syntax/exceptions/test_type_mismatch_exception.py b/tests/functional/syntax/exceptions/test_type_mismatch_exception.py
index 514f2df6..76c5c481 100644
--- a/tests/functional/syntax/exceptions/test_type_mismatch_exception.py
+++ b/tests/functional/syntax/exceptions/test_type_mismatch_exception.py
@@ -41,7 +41,7 @@ event Foo:
     message: String[1]
 @external
 def foo():
-    log Foo("abcd")
+    log Foo(message="abcd")
     """,
     # Address literal must be checksummed
     """
diff --git a/tests/functional/syntax/names/test_event_names.py b/tests/functional/syntax/names/test_event_names.py
index 367b646b..28cd6bda 100644
--- a/tests/functional/syntax/names/test_event_names.py
+++ b/tests/functional/syntax/names/test_event_names.py
@@ -26,7 +26,7 @@ event int128:
 @external
 def foo(i: int128) -> int128:
     temp_var : int128 = i
-    log int128(temp_var)
+    log int128(variable=temp_var)
     return temp_var
     """,
         NamespaceCollision,
@@ -39,7 +39,7 @@ event decimal:
 @external
 def foo(i: int128) -> int128:
     temp_var : int128 = i
-    log decimal(temp_var)
+    log decimal(variable=temp_var)
     return temp_var
     """,
         NamespaceCollision,
@@ -52,7 +52,7 @@ event wei:
 @external
 def foo(i: int128) -> int128:
     temp_var : int128 = i
-    log wei(temp_var)
+    log wei(variable=temp_var)
     return temp_var
     """,
         StructureException,
@@ -65,7 +65,7 @@ event false:
 @external
 def foo(i: int128) -> int128:
     temp_var : int128 = i
-    log false(temp_var)
+    log false(variable=temp_var)
     return temp_var
     """,
         StructureException,
@@ -102,7 +102,7 @@ event Assigned:
 @external
 def foo(i: int128) -> int128:
     variable : int128 = i
-    log Assigned(variable)
+    log Assigned(variable=variable)
     return variable
     """,
     """
@@ -122,7 +122,7 @@ event Assigned1:
 @external
 def foo(i: int128) -> int128:
     variable : int128 = i
-    log Assigned1(variable)
+    log Assigned1(variable=variable)
     return variable
     """,
 ]
diff --git a/tests/functional/syntax/test_ann_assign.py b/tests/functional/syntax/test_ann_assign.py
index 23ebeb95..fba9eff3 100644
--- a/tests/functional/syntax/test_ann_assign.py
+++ b/tests/functional/syntax/test_ann_assign.py
@@ -3,11 +3,11 @@ from pytest import raises
 
 from vyper import compiler
 from vyper.exceptions import (
+    InstantiationException,
     InvalidAttribute,
     TypeMismatch,
     UndeclaredDefinition,
     UnknownAttribute,
-    VariableDeclarationException,
 )
 
 fail_list = [
@@ -73,7 +73,7 @@ struct S:
 def foo() -> int128:
     s: S = S(a=1)
     """,
-        VariableDeclarationException,
+        InstantiationException,
     ),
     (
         """
diff --git a/tests/functional/syntax/test_external_calls.py b/tests/functional/syntax/test_external_calls.py
index a8fb5ae8..fd6fa28c 100644
--- a/tests/functional/syntax/test_external_calls.py
+++ b/tests/functional/syntax/test_external_calls.py
@@ -61,7 +61,7 @@ def foo(f: Foo):
     s: uint256 = staticcall f.foo()
     """,
     # TODO: tokenizer currently has issue with log+staticcall/extcall, e.g.
-    # `log Bar(staticcall f.foo() + extcall f.bar())`
+    # `log Bar(_value=staticcall f.foo() + extcall f.bar())`
 ]
 
 
diff --git a/tests/functional/syntax/test_interfaces.py b/tests/functional/syntax/test_interfaces.py
index ea06e0ab..20813c48 100644
--- a/tests/functional/syntax/test_interfaces.py
+++ b/tests/functional/syntax/test_interfaces.py
@@ -158,12 +158,12 @@ totalSupply: public(uint256)
 
 @external
 def transfer(_to : address, _value : uint256) -> bool:
-    log Transfer(msg.sender, _to, _value)
+    log Transfer(sender=msg.sender, receiver=_to, value=_value)
     return True
 
 @external
 def transferFrom(_from : address, _to : address, _value : uint256) -> bool:
-    log IERC20.Transfer(_from, _to, _value)
+    log IERC20.Transfer(sender=_from, receiver=_to, value=_value)
     return True
 
 @external
diff --git a/tests/functional/syntax/test_logging.py b/tests/functional/syntax/test_logging.py
index b96700a1..7f8f141b 100644
--- a/tests/functional/syntax/test_logging.py
+++ b/tests/functional/syntax/test_logging.py
@@ -1,7 +1,13 @@
 import pytest
 
 from vyper import compiler
-from vyper.exceptions import StructureException, TypeMismatch
+from vyper.exceptions import (
+    InstantiationException,
+    InvalidAttribute,
+    StructureException,
+    TypeMismatch,
+    UnknownAttribute,
+)
 
 fail_list = [
     """
@@ -12,7 +18,7 @@ x: decimal[4]
 
 @external
 def foo():
-    log Bar(self.x)
+    log Bar(_value=self.x)
     """,
     """
 event Bar:
@@ -21,7 +27,7 @@ event Bar:
 @external
 def foo():
     x: decimal[4] = [0.0, 0.0, 0.0, 0.0]
-    log Bar(x)
+    log Bar(_value=x)
     """,
     """
 struct Foo:
@@ -37,7 +43,7 @@ event Test:
 
 @external
 def test():
-    log Test(-7)
+    log Test(n=-7)
    """,
 ]
 
@@ -48,6 +54,61 @@ def test_logging_fail(bad_code):
         compiler.compile_code(bad_code)
 
 
+def test_logging_fail_mixed_positional_kwargs():
+    code = """
+event Test:
+    n: uint256
+    o: uint256
+
+@external
+def test():
+    log Test(7, o=12)
+    """
+    with pytest.raises(InstantiationException):
+        compiler.compile_code(code)
+
+
+def test_logging_fail_unknown_kwarg():
+    code = """
+event Test:
+    n: uint256
+
+@external
+def test():
+    log Test(n=7, o=12)
+    """
+    with pytest.raises(UnknownAttribute):
+        compiler.compile_code(code)
+
+
+def test_logging_fail_missing_kwarg():
+    code = """
+event Test:
+    n: uint256
+    o: uint256
+
+@external
+def test():
+    log Test(n=7)
+    """
+    with pytest.raises(InstantiationException):
+        compiler.compile_code(code)
+
+
+def test_logging_fail_kwargs_out_of_order():
+    code = """
+event Test:
+    n: uint256
+    o: uint256
+
+@external
+def test():
+    log Test(o=12, n=7)
+    """
+    with pytest.raises(InvalidAttribute):
+        compiler.compile_code(code)
+
+
 @pytest.mark.parametrize("mutability", ["@pure", "@view"])
 @pytest.mark.parametrize("visibility", ["@internal", "@external"])
 def test_logging_from_non_mutable(mutability, visibility):
@@ -58,7 +119,23 @@ event Test:
 {visibility}
 {mutability}
 def test():
-    log Test(1)
+    log Test(n=1)
     """
     with pytest.raises(StructureException):
         compiler.compile_code(code)
+
+
+def test_logging_with_positional_args(get_contract, get_logs):
+    # TODO: Remove when positional arguments are fully deprecated
+    code = """
+event Test:
+    n: uint256
+
+@external
+def test():
+    log Test(1)
+    """
+    c = get_contract(code)
+    c.test()
+    (log,) = get_logs(c, "Test")
+    assert log.args.n == 1
diff --git a/tests/functional/syntax/test_structs.py b/tests/functional/syntax/test_structs.py
index 9a9a397c..c08859cd 100644
--- a/tests/functional/syntax/test_structs.py
+++ b/tests/functional/syntax/test_structs.py
@@ -5,6 +5,7 @@ import pytest
 from vyper import compiler
 from vyper.exceptions import (
     InstantiationException,
+    InvalidAttribute,
     StructureException,
     SyntaxException,
     TypeMismatch,
@@ -32,7 +33,8 @@ def foo():
     """,
         UnknownAttribute,
     ),
-    """
+    (
+        """
 struct A:
     x: int128
     y: int128
@@ -41,6 +43,8 @@ a: A
 def foo():
     self.a = A(x=1)
     """,
+        InstantiationException,
+    ),
     """
 struct A:
     x: int128
@@ -61,7 +65,8 @@ b: A
 def foo():
     self.a = A(self.b)
     """,
-    """
+    (
+        """
 struct A:
     x: int128
     y: int128
@@ -70,6 +75,8 @@ a: A
 def foo():
     self.a = A({x: 1})
     """,
+        InstantiationException,
+    ),
     """
 struct C:
     c: int128
@@ -386,7 +393,7 @@ b: B
 def foo():
     self.b = B(foo=1, foo=2)
     """,
-        UnknownAttribute,
+        InvalidAttribute,
     ),
     (
         """