Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DOCS] Add clarifications to mapping type #7100

Merged
merged 1 commit into from
Nov 6, 2019
Merged

Conversation

ChrisChinchilla
Copy link
Contributor

@ChrisChinchilla ChrisChinchilla commented Jul 15, 2019

Description

Closes #6303 by adding some description around the code example, and adding a link to the type when mentioned in other places.

Checklist

  • Code compiles correctly
  • All tests are passing
  • New tests have been created which fail without the change (if possible)
  • README / documentation was extended, if necessary
  • Changelog entry (if change is visible to the user)
  • Used meaningful commit messages


In the example below, the ``MappingExample`` contract defines a public ``balances``
mapping, with the key type an ``address``, and a value type a ``uint``, mapping
an integer value to an Ethereum address. As ``uint`` is a value type, the getter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mapping an address to an unsigned integer.

@ChrisChinchilla
Copy link
Contributor Author

@chriseth I understand your last comment, and I understand the principles, but as this was the content that was already there, I'm struggling slightly to explain it better.

The pseudo syntax refers to the earlier paragraph, and I am hoping that the example (and explanation) help explain it…

@@ -4,7 +4,7 @@
Mapping Types
=============

You declare mapping types with the syntax ``mapping(_KeyType => _ValueType)``.
You declare mapping types with the syntax ``mapping(_KeyType => _ValueType) _VariableModifiers _VariableName``.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how you declare a variable of mapping type, not how you declare a mapping type.

Maybe declare is also the wrong word, use may be better.

:ref:`getter <visibility-and-getters>` for you. The ``_KeyType`` becomes a
parameter for the getter. If ``_ValueType`` is a value type or a struct,
the getter returns ``_ValueType``.
:ref:`getter <visibility-and-getters>` for you. The ``_KeyType`` is the parameter for the getter.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep "becomes a", because it may not be the only parameter.

@ChrisChinchilla
Copy link
Contributor Author

Changes made

@@ -4,7 +4,8 @@
Mapping Types
=============

You declare mapping types with the syntax ``mapping(_KeyType => _ValueType)``.
Mapping types use the syntax ``mapping(_KeyType => _ValueType)`` and variables
are declared as a mapping type using the syntax ``mapping (_KeyType => _ValueType) _VariableModifiers _VariableName``.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably not too useful to keep this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still do not think it is useful to keep this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which bit? It was here already, and from the conversations I've had around, people are really confused by the mapping syntax, so I'd love to try and be as explicit and clear as possible. Any suggestions to keep that clarity but express it differently?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to give an example how to declare a specific variable instead of just adding random placeholders whose meaning is not explained.

point to remember are that the mapping inside the mapping is not a return variable,
but the variable inside it is.

::
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chriseth Myself and @ekpyron had a chat about an example for a mapping with a mapping and struggled to think of many great examples. What do you think of this one? Then I'll polish the text.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good!

@@ -52,6 +58,34 @@ each ``_KeyType``, recursively. For example with a mapping:
}
}

The example below uses a mapping type inside another mapping type. The important
point to remember are that the mapping inside the mapping is not a return variable,
but the variable inside it is.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"value" instead of 'variable' for both?

function f() public returns (bool) {
MappingExample m = new MappingExample();
m.transfer(address(this),address(this));
return m.validBalances(address(this),address(this));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return m.validBalances(address(this),address(this));
return m.validBalances(address(this), address(this));

pragma solidity >=0.4.0 <0.7.0;

contract MappingExample {
mapping(address => mapping(address => bool)) public validBalances;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't erc20 use something like allowances?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does, but I'm not sure what you mean? This isn't really an ERC20 implementation, and the data structure here is different too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's exactly the same - and I think it would be a good example: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol#L36

@ChrisChinchilla
Copy link
Contributor Author

@chriseth Made changes, i think I corrected another code error too.

@chriseth
Copy link
Contributor

chriseth commented Sep 4, 2019

Tests are failing.

each ``_KeyType``, recursively.

In the example below, the ``MappingExample`` contract defines a public ``balances``
mapping, with the key type an ``address``, and a value type a ``uint``, mapping
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it mapping the address to the uint?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that is what I was trying to say, but I'll make it clearer.


pragma solidity >=0.4.0 <0.7.0;

contract MappingExample {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think a full credit / allowance example would be too long?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chriseth Possibly, but how much do you think the full example relates to mappings and how they work? Or maybe a reduced example of what you suggest that highlights those aspects? I assume based on the Zeppelin link from above?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can use the following functions and also slim them down a little:

  • approve
  • transferFrom
  • allowances

@@ -52,6 +58,34 @@ each ``_KeyType``, recursively. For example with a mapping:
}
}

The example below uses a mapping type inside another mapping type. The important
point to remember are that the mapping inside the mapping is not the return value,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should be The important point to remember is not are... but also: "the return value" of what? I'm not sure I understand the purpose of this sentence :-).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected that. The point is to clarify that the returned value isn't the mapping inside the mapping, but the value inside the nested mapping. That's what I understood from you anyway?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you're talking about "the returned value", you have to say what you are talking about that returns something - the index access of the outer mapping? The index access of the outer mapping in fact does return "the mapping inside the mapping" - only the second index access yields the value contained in the inner mapping...

contract C {
    mapping(address => mapping(address => bool)) public allowances;
    function f(address X, address Y) public view {
        mapping(address => bool) storage allowancesForX = allowances[X]; // first index access is a mapping
        bool allowanceForXandY = allowances[X][Y]; // two index accesses give the contained value
        bool allowanceForXandY2 = allowancesForX[Y]; // which is the same as accessing the contained mapping at Y
        assert(allowanceForXandY == allowanceForXandY2);
    }
}

@ChrisChinchilla
Copy link
Contributor Author

OK @chriseth done!

I had to cut a fair bit out, so I hope nothing got lost in the process. @ekpyron this also cut most of the copy that we were discussing, possibly it even needs more explanation now!


function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
_transfer(sender, recipient, amount);
_approve(sender, msg.sender, amount);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you call approve here? It should rather check that the allowance is large enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chriseth This came from the Zeppelin code…

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it does something else now.

Copy link
Collaborator

@erak erak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to rebased since the failing test was fixed with #7419.

@ChrisChinchilla ChrisChinchilla force-pushed the docs-review-mappings branch 2 times, most recently from a391523 to ddc9282 Compare October 7, 2019 03:42
@ChrisChinchilla
Copy link
Contributor Author

@erak Rebased, squashed

In the example below, the ``MappingExample`` contract defines a public ``balances``
mapping, with the key type an ``address``, and a value type a ``uint``, mapping
an Ethereum address to an unsigned integer value. As ``uint`` is a value type, the getter
returns a value that matches the type, which you can see in the ``MappingUser``
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"of that type" (how does a value match a type?)

emit Transfer(sender, recipient, amount);
}

function _approve(address owner, address spender, uint256 amount) internal {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is better to combine this function with approve and handle the approval reduction in transferFrom manually.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is what you're asking for…

chriseth
chriseth previously approved these changes Nov 6, 2019
Updates from review

Changes from review

Nested example

Update docs/types/mapping-types.rst

Co-Authored-By: chriseth <[email protected]>
Changes from review

Bring example inline with ERC20

Clarify what maps where

Use OZ contract example

Update docs/types/mapping-types.rst

Co-Authored-By: chriseth <[email protected]>

update code example
@chriseth
Copy link
Contributor

chriseth commented Nov 6, 2019

Rebased.

Copy link
Contributor

@chriseth chriseth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still not sure about some details, but better to merge this.

@chriseth chriseth merged commit efeee15 into develop Nov 6, 2019
@chriseth chriseth deleted the docs-review-mappings branch November 6, 2019 20:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[DOCS] Review and potentially clarify mappings docs
5 participants