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

feat: stateful precompiles #42

Open
wants to merge 9 commits into
base: release/1.13
Choose a base branch
from

Conversation

mycodecrafting
Copy link

@mycodecrafting mycodecrafting commented Feb 27, 2024

Summary

Add ability for stateful precompiles into the evm.

Stateful precompiles have access to:

  • evm StateDB, Get and Set (scope limited to precompile's address)
  • Native token balances, Get, Subtract, and Add
  • self address
  • message sender
  • message value

Includes example implementation of a native minter precompile

@mycodecrafting mycodecrafting added the enhancement New feature or request label Feb 27, 2024
@mycodecrafting mycodecrafting marked this pull request as ready for review March 6, 2024 22:29
@joroshiba joroshiba requested a review from noot April 2, 2024 14:55
Comment on lines +162 to +163
// evm.precompileManager.Register(common.HexToAddress("0x1001"), compress.NewCompress())
// evm.precompileManager.Register(common.HexToAddress("0x1002"), jsonutil.NewJsonUtil())
Copy link

Choose a reason for hiding this comment

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

remove

Comment on lines +156 to +160
// e.g. register native minter to 0x0000000000000000000000000000000000001000
evm.precompileManager.Register(
common.HexToAddress("0x1000"),
nativeminter.NewNativeMinter(),
)
Copy link

Choose a reason for hiding this comment

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

do we want to have this functionality here? probably would be better to move to an example branch

Copy link
Author

Choose a reason for hiding this comment

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

No, it def needs removed, at least registering one. Could probably just be moved to a precompile doc that explains a bit and shows examples

Comment on lines +97 to +102
// PrecompileManager registers and runs stateful precompiles
type PrecompileManager interface {
IsPrecompile(addr common.Address) bool
Run(addr common.Address, input []byte, caller common.Address, value *big.Int, suppliedGas uint64, readonly bool) (ret []byte, remainingGas uint64, err error)
Register(addr common.Address, p precompile.StatefulPrecompiledContract) error
}
Copy link

Choose a reason for hiding this comment

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

why do we need the interface? are there multiple implementations of this?

Copy link
Author

Choose a reason for hiding this comment

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

There are not. I don't recall why I made the interface. Will remove.

return fmt.Errorf("precompiled contract already exists at address %v", addr.Hex())
}

// niaeve implementation; parsed abi method names must match precompile method names 1:1
Copy link

Choose a reason for hiding this comment

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

Suggested change
// niaeve implementation; parsed abi method names must match precompile method names 1:1
// naive implementation; parsed abi method names must match precompile method names 1:1

Comment on lines +19 to +21
StatefulPrecompiledContract: precompile.NewStatefulPrecompiledContract(
bindings.NativeMinterABI,
),
Copy link

Choose a reason for hiding this comment

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

where is this used? i don't see this used anywhere. i'm also somewhat confused on why there are both native and solidity implementations when it looks like the native is actually implementing the storage changes, not doing it via calling the contract

Copy link

Choose a reason for hiding this comment

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

is this just used for returning the ABI? if so I would change this to get rid of the solidity/generated bindings and just manually create a function in this file that returns the contract function signature -> native method

Copy link
Author

Choose a reason for hiding this comment

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

The solidity interface serves to both be used for ABI gen (which is being used for the function mapping) and also as an integration point for other contracts to call the precompiles, e.g

nativeMinter = INativeMinter(_precompileAddr);
nativeMinter.mint(_recipient, _amount);

Copy link
Author

Choose a reason for hiding this comment

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

oh yeah the generated ABI is also being used to unpack the input to pass to the native method and pack the return into the output. The generated bindings aren't used at all tho, just the generated ABI string. Could maybe clean that up some by having the ABI as a const in the precompile contract native code.

))

// check if precompile returned an error
if len(results) > 0 {
Copy link

@noot noot Apr 2, 2024

Choose a reason for hiding this comment

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

can we check that the result parameters (length, type) are what are expected based on the method signature?

Copy link
Author

Choose a reason for hiding this comment

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

makes sense, will add

Comment on lines +125 to +128
output, err = method.abiMethod.Outputs.Pack(interfaceArgs...)
if err != nil {
return nil, 0, err
}
Copy link

Choose a reason for hiding this comment

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

related to above - if the number/type of args aren't correct, this will probably error?

}
}

suppliedGas -= gasCost
Copy link

Choose a reason for hiding this comment

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

does the caller check if this went negative? probably but just want to confirm

Copy link
Author

Choose a reason for hiding this comment

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

There's a check above, Line 77,

	gasCost := contract.RequiredGas(input)
	if gasCost > suppliedGas {
		return nil, 0, ErrOutOfGas
	}

// The method name of the first one will be resolved as Foo while the second one
// will be resolved as Foo0.
//
// Alternatively could require each precompile to define the func mapping instead of doing this magic
Copy link

Choose a reason for hiding this comment

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

i think this is a lot better - the solidity/abigen stuff is unused apart from the abi, so we should remove it and just have the native code define the mapping.

return spc.abi
}

func (spc *statefulPrecompiledContract) RequiredGas(input []byte) uint64 {
Copy link

Choose a reason for hiding this comment

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

the contract has multiple methods, shouldn't gas be defined for each method or specifically each state access?

Copy link
Author

Choose a reason for hiding this comment

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

you technically can with this if you decode input, but that's some heavy lifting every time you want to have different gas for each method. I have yet to come up with a good way to allow each method to define it's own gas that doesn't use some reflect black magic. Open to ideas

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants