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

Migrate warp precompile #391

Merged
merged 30 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c30bee7
Migrate warp backend from Subnet-EVM
aaronbuchwald Nov 13, 2023
1a36626
warp/handlers: update duration greater than assertion to pass on windows
aaronbuchwald Nov 13, 2023
c38a9ef
Update precompile setup for warp
aaronbuchwald Nov 13, 2023
2ddaa54
Pass predicate context through block build / verify
aaronbuchwald Nov 14, 2023
9a6aa1d
Fix up dynamic fee extra data window handling post D-Upgrade
aaronbuchwald Nov 14, 2023
c04403a
Migrate x/warp package and start migrating tests
aaronbuchwald Nov 14, 2023
90013aa
Fix most of the tests
aaronbuchwald Nov 14, 2023
5f0218f
Copy chain configs in vm initialize to fix test
aaronbuchwald Nov 14, 2023
17909e8
Remove duration stats test from warp network handler
aaronbuchwald Nov 14, 2023
ad5959e
Merge branch 'warp-backend' into warp-precompile-setup
aaronbuchwald Nov 14, 2023
73bd5f4
Fix trailing newline
aaronbuchwald Nov 14, 2023
69864a9
Merge branch 'warp-precompile-setup' into migrate-warp-precompile
aaronbuchwald Nov 14, 2023
c557b7f
Move aggregateSignatures log to add more detail
aaronbuchwald Nov 16, 2023
1636af4
Add subnetIDStr option to warp p2p API server/client
aaronbuchwald Nov 16, 2023
2daa47b
Merge branch 'master' into warp-backend
aaronbuchwald Nov 17, 2023
1f73db7
Update signature aggregator constructor comment
aaronbuchwald Nov 17, 2023
71ccb87
Add back CanTransferMC and TransferMultiCoin
aaronbuchwald Nov 17, 2023
e6f8ca2
Remove duplicate from reserved address space
aaronbuchwald Nov 17, 2023
65d3368
remove duplicate check in different package
aaronbuchwald Nov 17, 2023
533a0af
Remove js related code from contracts/
aaronbuchwald Nov 17, 2023
dea508e
Update README to apply to both Coreth and Subnet-EVM
aaronbuchwald Nov 17, 2023
9711885
Update warp/aggregator/aggregator.go
aaronbuchwald Nov 27, 2023
7c0a0c2
Merge branch 'master' into warp-backend
aaronbuchwald Nov 28, 2023
3853d48
core/vm: move native asset contracts to separate file
aaronbuchwald Nov 28, 2023
676bcac
Remove unused istanbul field from txpool
aaronbuchwald Nov 28, 2023
d1bb80b
Merge branch 'warp-backend' into warp-precompile-setup
aaronbuchwald Nov 28, 2023
8b12e57
Merge branch 'warp-precompile-setup' into migrate-warp-precompile
aaronbuchwald Nov 28, 2023
5ce4692
force load precompile registry from vm.go
aaronbuchwald Nov 28, 2023
8719eea
use packTopic in MakeTopics
aaronbuchwald Nov 28, 2023
52acc1b
Merge branch 'master' into migrate-warp-precompile
aaronbuchwald Nov 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
49 changes: 49 additions & 0 deletions accounts/abi/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,55 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
return append(method.ID, arguments...), nil
}

// PackEvent packs the given event name and arguments to conform the ABI.
// Returns the topics for the event including the event signature (if non-anonymous event) and
// hashes derived from indexed arguments and the packed data of non-indexed args according to
// the event ABI specification.
// The order of arguments must match the order of the event definition.
// https://docs.soliditylang.org/en/v0.8.17/abi-spec.html#indexed-event-encoding.
// Note: PackEvent does not support array (fixed or dynamic-size) or struct types.
func (abi ABI) PackEvent(name string, args ...interface{}) ([]common.Hash, []byte, error) {
event, exist := abi.Events[name]
if !exist {
return nil, nil, fmt.Errorf("event '%s' not found", name)
}
if len(args) != len(event.Inputs) {
return nil, nil, fmt.Errorf("event '%s' unexpected number of inputs %d", name, len(args))
}

var (
nonIndexedInputs = make([]interface{}, 0)
indexedInputs = make([]interface{}, 0)
nonIndexedArgs Arguments
indexedArgs Arguments
)

for i, arg := range event.Inputs {
if arg.Indexed {
indexedArgs = append(indexedArgs, arg)
indexedInputs = append(indexedInputs, args[i])
} else {
nonIndexedArgs = append(nonIndexedArgs, arg)
nonIndexedInputs = append(nonIndexedInputs, args[i])
}
}

packedArguments, err := nonIndexedArgs.Pack(nonIndexedInputs...)
if err != nil {
return nil, nil, err
}
topics := make([]common.Hash, 0, len(indexedArgs)+1)
if !event.Anonymous {
topics = append(topics, event.ID)
}
indexedTopics, err := PackTopics(indexedInputs)
if err != nil {
return nil, nil, err
}

return append(topics, indexedTopics...), packedArguments, nil
}

// PackOutput packs the given [args] as the output of given method [name] to conform the ABI.
// This does not include method ID.
func (abi ABI) PackOutput(name string, args ...interface{}) ([]byte, error) {
Expand Down
96 changes: 96 additions & 0 deletions accounts/abi/abi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/assert"
)

const jsondata = `
Expand Down Expand Up @@ -1202,3 +1203,98 @@ func TestUnpackRevert(t *testing.T) {
})
}
}

func TestABI_PackEvent(t *testing.T) {
tests := []struct {
name string
json string
event string
args []interface{}
expectedTopics []common.Hash
expectedData []byte
}{
{
name: "received",
json: `[
{"type":"event","name":"received","anonymous":false,"inputs":[
{"indexed":false,"name":"sender","type":"address"},
{"indexed":false,"name":"amount","type":"uint256"},
{"indexed":false,"name":"memo","type":"bytes"}
]
}]`,
event: "received(address,uint256,bytes)",
args: []interface{}{
common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
big.NewInt(1),
[]byte{0x88},
},
expectedTopics: []common.Hash{
common.HexToHash("0x75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed"),
},
expectedData: common.Hex2Bytes("000000000000000000000000376c47978271565f56deb45495afa69e59c16ab20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000018800000000000000000000000000000000000000000000000000000000000000"),
},
{
name: "received",
json: `[
{"type":"event","name":"received","anonymous":true,"inputs":[
{"indexed":false,"name":"sender","type":"address"},
{"indexed":false,"name":"amount","type":"uint256"},
{"indexed":false,"name":"memo","type":"bytes"}
]
}]`,
event: "received(address,uint256,bytes)",
args: []interface{}{
common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
big.NewInt(1),
[]byte{0x88},
},
expectedTopics: []common.Hash{},
expectedData: common.Hex2Bytes("000000000000000000000000376c47978271565f56deb45495afa69e59c16ab20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000018800000000000000000000000000000000000000000000000000000000000000"),
}, {
name: "Transfer",
json: `[
{ "constant": true, "inputs": [], "name": "name", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" },
{ "constant": false, "inputs": [ { "name": "_spender", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "approve", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" },
{ "constant": true, "inputs": [], "name": "totalSupply", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" },
{ "constant": false, "inputs": [ { "name": "_from", "type": "address" }, { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "transferFrom", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" },
{ "constant": true, "inputs": [], "name": "decimals", "outputs": [ { "name": "", "type": "uint8" } ], "payable": false, "stateMutability": "view", "type": "function" },
{ "constant": true, "inputs": [ { "name": "_owner", "type": "address" } ], "name": "balanceOf", "outputs": [ { "name": "balance", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" },
{ "constant": true, "inputs": [], "name": "symbol", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" },
{ "constant": false, "inputs": [ { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "transfer", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" },
{ "constant": true, "inputs": [ { "name": "_owner", "type": "address" }, { "name": "_spender", "type": "address" } ], "name": "allowance", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" },
{ "payable": true, "stateMutability": "payable", "type": "fallback" },
{ "anonymous": false, "inputs": [ { "indexed": true, "name": "owner", "type": "address" }, { "indexed": true, "name": "spender", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Approval", "type": "event" },
{ "anonymous": false, "inputs": [ { "indexed": true, "name": "from", "type": "address" }, { "indexed": true, "name": "to", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Transfer", "type": "event" }
]`,
event: "Transfer(address,address,uint256)",
args: []interface{}{
common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
big.NewInt(100),
},
expectedTopics: []common.Hash{
common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
common.HexToHash("0x0000000000000000000000008db97c7cece249c2b98bdc0226cc4c2a57bf52fc"),
common.HexToHash("0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab2"),
},
expectedData: common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000064"),
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
abi, err := JSON(strings.NewReader(test.json))
if err != nil {
t.Error(err)
}

topics, data, err := abi.PackEvent(test.name, test.args...)
if err != nil {
t.Fatal(err)
}

assert.EqualValues(t, test.expectedTopics, topics)
assert.EqualValues(t, test.expectedData, data)
})
}
}
145 changes: 86 additions & 59 deletions accounts/abi/topics.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,70 +37,97 @@ import (
"github.com/ethereum/go-ethereum/crypto"
)

// packTopic packs rule into the corresponding hash value for a log's topic
// according to the Solidity documentation:
// https://docs.soliditylang.org/en/v0.8.17/abi-spec.html#indexed-event-encoding.
func packTopic(rule interface{}) (common.Hash, error) {
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
var topic common.Hash

// Try to generate the topic based on simple types
switch rule := rule.(type) {
case common.Hash:
copy(topic[:], rule[:])
case common.Address:
copy(topic[common.HashLength-common.AddressLength:], rule[:])
case *big.Int:
blob := rule.Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case bool:
if rule {
topic[common.HashLength-1] = 1
}
case int8:
copy(topic[:], genIntType(int64(rule), 1))
case int16:
copy(topic[:], genIntType(int64(rule), 2))
case int32:
copy(topic[:], genIntType(int64(rule), 4))
case int64:
copy(topic[:], genIntType(rule, 8))
case uint8:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint16:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint32:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint64:
blob := new(big.Int).SetUint64(rule).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case string:
hash := crypto.Keccak256Hash([]byte(rule))
copy(topic[:], hash[:])
case []byte:
hash := crypto.Keccak256Hash(rule)
copy(topic[:], hash[:])

default:
// todo(rjl493456442) according solidity documentation, indexed event
// parameters that are not value types i.e. arrays and structs are not
// stored directly but instead a keccak256-hash of an encoding is stored.
//
// We only convert strings and bytes to hash, still need to deal with
// array(both fixed-size and dynamic-size) and struct.

// Attempt to generate the topic from funky types
val := reflect.ValueOf(rule)
switch {
// static byte array
case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8:
reflect.Copy(reflect.ValueOf(topic[:val.Len()]), val)
default:
return common.Hash{}, fmt.Errorf("unsupported indexed type: %T", rule)
}
}
return topic, nil
}

// PackTopics packs the array of filters into an array of corresponding topics
// according to the Solidity documentation.
// Note: PackTopics does not support array (fixed or dynamic-size) or struct types.
func PackTopics(filter []interface{}) ([]common.Hash, error) {
topics := make([]common.Hash, len(filter))
for i, rule := range filter {
topic, err := packTopic(rule)
if err != nil {
return nil, err
}
topics[i] = topic
}

return topics, nil
}

// MakeTopics converts a filter query argument list into a filter topic set.
func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) {
topics := make([][]common.Hash, len(query))
for i, filter := range query {
for _, rule := range filter {
var topic common.Hash

// Try to generate the topic based on simple types
switch rule := rule.(type) {
case common.Hash:
copy(topic[:], rule[:])
case common.Address:
copy(topic[common.HashLength-common.AddressLength:], rule[:])
case *big.Int:
blob := rule.Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case bool:
if rule {
topic[common.HashLength-1] = 1
}
case int8:
copy(topic[:], genIntType(int64(rule), 1))
case int16:
copy(topic[:], genIntType(int64(rule), 2))
case int32:
copy(topic[:], genIntType(int64(rule), 4))
case int64:
copy(topic[:], genIntType(rule, 8))
case uint8:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint16:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint32:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint64:
blob := new(big.Int).SetUint64(rule).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case string:
hash := crypto.Keccak256Hash([]byte(rule))
copy(topic[:], hash[:])
case []byte:
hash := crypto.Keccak256Hash(rule)
copy(topic[:], hash[:])

default:
// todo(rjl493456442) according solidity documentation, indexed event
// parameters that are not value types i.e. arrays and structs are not
// stored directly but instead a keccak256-hash of an encoding is stored.
//
// We only convert stringS and bytes to hash, still need to deal with
// array(both fixed-size and dynamic-size) and struct.

// Attempt to generate the topic from funky types
val := reflect.ValueOf(rule)
switch {
// static byte array
case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8:
reflect.Copy(reflect.ValueOf(topic[:val.Len()]), val)
default:
return nil, fmt.Errorf("unsupported indexed type: %T", rule)
}
topic, err := packTopic(rule)
if err != nil {
return nil, err
}
topics[i] = append(topics[i], topic)
}
Expand Down
58 changes: 58 additions & 0 deletions contracts/contracts/ExampleWarp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;

import "./interfaces/IWarpMessenger.sol";

contract ExampleWarp {
address constant WARP_ADDRESS = 0x0200000000000000000000000000000000000005;
IWarpMessenger warp = IWarpMessenger(WARP_ADDRESS);

// sendWarpMessage sends a warp message containing the payload
function sendWarpMessage(bytes calldata payload) external {
warp.sendWarpMessage(payload);
}

// validateWarpMessage retrieves the warp message attached to the transaction and verifies all of its attributes.
function validateWarpMessage(
uint32 index,
bytes32 sourceChainID,
address originSenderAddress,
bytes calldata payload
) external view {
(WarpMessage memory message, bool valid) = warp.getVerifiedWarpMessage(index);
require(valid);
require(message.sourceChainID == sourceChainID);
require(message.originSenderAddress == originSenderAddress);
require(keccak256(message.payload) == keccak256(payload));
}

function validateInvalidWarpMessage(uint32 index) external view {
(WarpMessage memory message, bool valid) = warp.getVerifiedWarpMessage(index);
require(!valid);
require(message.sourceChainID == bytes32(0));
require(message.originSenderAddress == address(0));
require(keccak256(message.payload) == keccak256(bytes("")));
}

// validateWarpBlockHash retrieves the warp block hash attached to the transaction and verifies it matches the
// expected block hash.
function validateWarpBlockHash(uint32 index, bytes32 sourceChainID, bytes32 blockHash) external view {
(WarpBlockHash memory warpBlockHash, bool valid) = warp.getVerifiedWarpBlockHash(index);
require(valid);
require(warpBlockHash.sourceChainID == sourceChainID);
require(warpBlockHash.blockHash == blockHash);
}

function validateInvalidWarpBlockHash(uint32 index) external view {
(WarpBlockHash memory warpBlockHash, bool valid) = warp.getVerifiedWarpBlockHash(index);
require(!valid);
require(warpBlockHash.sourceChainID == bytes32(0));
require(warpBlockHash.blockHash == bytes32(0));
}

// validateGetBlockchainID checks that the blockchainID returned by warp matches the argument
function validateGetBlockchainID(bytes32 blockchainID) external view {
require(blockchainID == warp.getBlockchainID());
}
}