Skip to content

Commit

Permalink
Migrate warp precompile (#391)
Browse files Browse the repository at this point in the history
* Migrate warp backend from Subnet-EVM

* warp/handlers: update duration greater than assertion to pass on windows

* Update precompile setup for warp

* Pass predicate context through block build / verify

* Fix up dynamic fee extra data window handling post D-Upgrade

* Migrate x/warp package and start migrating tests

* Fix most of the tests

* Copy chain configs in vm initialize to fix test

* Remove duration stats test from warp network handler

* Fix trailing newline

* Move aggregateSignatures log to add more detail

* Add subnetIDStr option to warp p2p API server/client

* Update signature aggregator constructor comment

* Add back CanTransferMC and TransferMultiCoin

* Remove duplicate from reserved address space

* remove duplicate check in different package

* Remove js related code from contracts/

* Update README to apply to both Coreth and Subnet-EVM

* Update warp/aggregator/aggregator.go

Co-authored-by: Ceyhun Onur <ceyhun.onur@avalabs.org>
Signed-off-by: aaronbuchwald <aaron.buchwald56@gmail.com>

* core/vm: move native asset contracts to separate file

* Remove unused istanbul field from txpool

* force load precompile registry from vm.go

* use packTopic in MakeTopics

---------

Signed-off-by: aaronbuchwald <aaron.buchwald56@gmail.com>
Co-authored-by: Ceyhun Onur <ceyhun.onur@avalabs.org>
  • Loading branch information
aaronbuchwald and ceyonur committed Nov 29, 2023
1 parent cbc0d3a commit f4ea162
Show file tree
Hide file tree
Showing 27 changed files with 4,501 additions and 85 deletions.
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) {
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());
}
}

0 comments on commit f4ea162

Please sign in to comment.