Skip to content
New issue

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

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

Already on GitHub? # to your account

Type checking for contract explicit storage base location #15528

Open
wants to merge 3 commits into
base: storageLocationsParserSupport
Choose a base branch
from

Conversation

matheusaaguiar
Copy link
Collaborator

@matheusaaguiar matheusaaguiar self-assigned this Oct 21, 2024
@matheusaaguiar matheusaaguiar changed the base branch from develop to storageLocationsParserSupport October 21, 2024 13:08
@github-actions github-actions bot added the stale The issue/PR was marked as stale because it has been open for too long. label Nov 5, 2024
@matheusaaguiar matheusaaguiar removed the stale The issue/PR was marked as stale because it has been open for too long. label Nov 8, 2024
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsParserSupport branch from bf640be to 666b499 Compare November 21, 2024 13:57
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsTypeChecking branch from d3cd13a to 5645314 Compare November 21, 2024 14:25
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsParserSupport branch from fb75168 to 2579486 Compare December 3, 2024 17:11
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsTypeChecking branch from 038b819 to 7f4eb69 Compare December 3, 2024 17:14
@github-actions github-actions bot added the stale The issue/PR was marked as stale because it has been open for too long. label Dec 25, 2024
@matheusaaguiar matheusaaguiar removed the stale The issue/PR was marked as stale because it has been open for too long. label Dec 26, 2024
@github-actions github-actions bot added the stale The issue/PR was marked as stale because it has been open for too long. label Jan 10, 2025
@matheusaaguiar matheusaaguiar removed the stale The issue/PR was marked as stale because it has been open for too long. label Jan 10, 2025
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsParserSupport branch from 07f289c to 6b34e90 Compare January 13, 2025 17:52
@ethereum ethereum deleted a comment from stackenbotten Jan 13, 2025
@ethereum ethereum deleted a comment from stackenbotten Jan 13, 2025
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsTypeChecking branch from 82b5fec to 54451ed Compare January 13, 2025 19:04
@matheusaaguiar matheusaaguiar marked this pull request as ready for review January 13, 2025 19:04
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsParserSupport branch from 6b34e90 to f0f7997 Compare January 17, 2025 15:14
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsTypeChecking branch from ff0c30b to f7f90be Compare January 17, 2025 15:14
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsParserSupport branch from f0f7997 to 2839eed Compare January 17, 2025 15:16
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsTypeChecking branch from f7f90be to 7473a01 Compare January 17, 2025 15:16
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsParserSupport branch from 7a42733 to c2d5d9a Compare January 28, 2025 03:46
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsTypeChecking branch from 6257c02 to 9acfb2d Compare January 28, 2025 04:30
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsParserSupport branch from 68f5886 to abca45e Compare January 28, 2025 23:11
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsTypeChecking branch from 7a1d6b2 to 6f29b3c Compare January 28, 2025 23:13
@cameel cameel added the has dependencies The PR depends on other PRs that must be merged first label Jan 31, 2025
@ethereum ethereum deleted a comment from github-actions bot Jan 31, 2025
@ethereum ethereum deleted a comment from github-actions bot Jan 31, 2025
@ethereum ethereum deleted a comment from github-actions bot Jan 31, 2025
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsParserSupport branch 2 times, most recently from e4e8e8e to 32daf59 Compare February 6, 2025 20:14
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsTypeChecking branch from e409300 to 7c76c68 Compare February 6, 2025 20:39
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsParserSupport branch from 32daf59 to 24a63fc Compare February 6, 2025 20:57
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsTypeChecking branch from 7c76c68 to a3e17ed Compare February 6, 2025 20:57
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsParserSupport branch from 24a63fc to f4ddc29 Compare February 6, 2025 21:16
Changelog.md Outdated Show resolved Hide resolved
@matheusaaguiar matheusaaguiar force-pushed the storageLocationsTypeChecking branch from a3e17ed to 3396801 Compare February 7, 2025 15:28
Co-authored-by: Kamil Śliwak <kamil.sliwak@codepoets.it>
Copy link
Member

@cameel cameel left a comment

Choose a reason for hiding this comment

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

Two main issues: layout on interfaces is not disallowed and also the issue unnecessarily restricts that a contract can only be inherited from once when there's a layout.

I also think that it would be good to move checks to parts of analysis other than TypeChecker. It seems perfectly feasible for this particular feature and TypeChecker is already a beast.

Other than that the usual stuff. Especially tests.

struct StorageLayoutSpecifierAnnotation: ASTAnnotation
{
// The evaluated value of the expression specifying the contract storage layout base
solidity::u256 value;
Copy link
Member

Choose a reason for hiding this comment

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

This is a pretty bad name :)

Also we're inside the solidity namespace here.

Suggested change
solidity::u256 value;
u256 baseSlot;

Copy link
Member

Choose a reason for hiding this comment

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

Also, you should make it SetOnce.

@@ -234,6 +236,99 @@ bool TypeChecker::visit(ImportDirective const&)
return false;
}

void TypeChecker::endVisit(ContractDefinition const& _contract)
{
if ((_contract.isLibrary() || _contract.abstract()) && _contract.storageLayoutSpecifier())
Copy link
Member

Choose a reason for hiding this comment

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

What about interfaces?

Comment on lines +1 to +5
contract A {}
contract B is A layout at 64 {}
contract C is A layout at 42 {}
// ----
// TypeError 2031: (46-77): Conflicting storage layout specifications:Storage layout for base contract 'A' was also specified by another contract which derives from it.
Copy link
Member

Choose a reason for hiding this comment

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

This is a perfectly valid case.

The derivedContractSpecifyingStorageLayout annotation seems to have been added just to check for it. You don't need it or the check. A contract that's not at the top-level of the hierarchy can be a part of multiple layouts.

m_errorReporter.typeError(
1139_error,
baseLocation->location(),
"The storage layout must be specified by an expression that can be evaluated at compilation time."
Copy link
Member

@cameel cameel Feb 7, 2025

Choose a reason for hiding this comment

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

Suggested change
"The storage layout must be specified by an expression that can be evaluated at compilation time."
"The base slot of the storage layout must be a compile-time constant expression."

Comment on lines +297 to +298
auto const* expressionType = type(*baseLocation);
BoolResult result = expressionType->isImplicitlyConvertibleTo(*TypeProvider::uint256());
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
auto const* expressionType = type(*baseLocation);
BoolResult result = expressionType->isImplicitlyConvertibleTo(*TypeProvider::uint256());
auto const* baseSlotExpressionType = type(*baseLocation);
BoolResult convertibleToUint = expressionType->isImplicitlyConvertibleTo(*TypeProvider::uint256());

return;
}

if (auto const* rationalType = dynamic_cast<RationalNumberType const*>(expressionType))
Copy link
Member

@cameel cameel Feb 7, 2025

Choose a reason for hiding this comment

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

IMO you should check this first, then fractionals, then range. Finally just assert that the value is convertible to uint and evaluate the value. This will make the message in most weird cases very clear (it's not a number literal). Currently you get a message about exceeding uint range in many weird cases, which suboptimal.

Later, when we open the functionality up to non-literals, we'll replace the assert with a message saying that type X is not implicitly convertible, which should also be clear.

if (auto const* rationalType = dynamic_cast<RationalNumberType const*>(expressionType))
{
solAssert(!rationalType->isFractional());
_storageLayoutSpecifier.annotation().value = u256(rationalType->value().numerator());
Copy link
Member

@cameel cameel Feb 7, 2025

Choose a reason for hiding this comment

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

Note that u256() conversion just truncates and does not throw when the value is out of range. You only indirectly check that it's in range, so you should also assert that it really is.

Also assert that denominator is in fact 1.

{
ASTPointer<Expression> const baseLocation = _storageLayoutSpecifier.expression();

if (!*baseLocation->annotation().isPure)
Copy link
Member

Choose a reason for hiding this comment

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

These checks would probably be better off in PostTypeChecker/PostTypeContractLevelChecker. TypeChecker is a beast already (4k+ lines) and we seem to be dumping everything in it :)

The checks and the annotation do not seem to be things that other checks will need to depend on so it should be fairly straightforward to move them. To the contrary - they use isPure annotation, which is fully filled out only after TypeChecker. We're in endVisit() so it should already be set on the expression, but still, it would be cleaner if all of it was not crammed into the same analysis pass.

Copy link
Member

Choose a reason for hiding this comment

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

Similarly, verifying that base contracts do not have layout specifiers seems like something that can be checked already by ContractLevelChecker and does not have to wait until we know the types.

Comment on lines 1 to +5
contract A { }
contract B { }
contract Z { }
contract C is A, B layout at 0x1234 { }
contract D layout at 0xABCD is A, B { }
contract D layout at 0xABCD is Z { }
Copy link
Member

Choose a reason for hiding this comment

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

This should be changed back.

Copy link
Member

Choose a reason for hiding this comment

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

Needs more tests. There's quite a few, but note that many of these will be simple one-liners and you can usually drop everything from one bullet point into one file because errors are not fatal.

The most necessary minimum:

  • Numbers with denominations (42 weeks, 666 gwei)
  • Rational numbers with zero fractional part (42.0, 2.5e10)
  • Interface with a layout
  • layout at 1 / 0
  • layout at -1

Scoping

  • Constants defined in inherited contracts
  • State variables defined in inherited contracts
  • Things defined in foreign contracts

Inheritance

  • Contract with a layout inheriting from an abstract contract/interface
  • Contract with a layout inheriting from itself
  • Inheriting from multiple contracts where first/last already has layout

Things that are now rejected but will have to work eventually so we may as well cover them already:

  • type(uint).max
  • uint(42)
  • uint40(bytes5(hex"0011223344"))
  • ~uint(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
  • [1, 2, 3][0]
  • string("ABCD").length
  • abi.decode(abi.encode(42), (uint))
  • uint(bytes32(type(I).interfaceId))
  • uint32(f.selector)
  • uint64(bytes8(bytes.concat("ABCD", "EFGH")))
  • uint(keccak256(bytes.concat("ABCD"))
  • uint8(b[1]), where b is bytes32 constant b
  • N / ~N where N is a constant.
  • Ternary operator
  • T.unwrap()

Wild things that should not work:

  • N / 0 where N is a constant
  • Expressions that would overflow in checked arithmetic
  • Expressions using custom operators defined on uint
  • Various non-uint literals: address, hex string, string, boolean, enum, [1, 2, 3], (1, 2, 3)
  • ++2, 1 = 2, delete 2
  • T.wrap()
  • f.selector, f.address
  • Builtins that depend on state: blockhash(), block.chainid, block.number, msg.value, tx.gasprice, <address>.balance, etc. Especially those that return uint.
  • Precompiles returning uint: addmod() etc.
  • Bound pure library function called on a literal (1.f())
  • Weird stuff: error, event, module, contract, type, magic, etc.
  • address(new C())
  • uint160(address(this))

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
has dependencies The PR depends on other PRs that must be merged first
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants