Skip to content

Commit

Permalink
Merge branch 'main' into golang-1.21.12
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelkaplan13 authored Jul 5, 2024
2 parents 06abd1c + 2bcc34f commit 6de0f52
Show file tree
Hide file tree
Showing 11 changed files with 1,456 additions and 14 deletions.
570 changes: 570 additions & 0 deletions abi-bindings/go/Utilities/ValidatorSetSig/ValidatorSetSig.go

Large diffs are not rendered by default.

72 changes: 72 additions & 0 deletions abi-bindings/go/Utilities/ValidatorSetSig/packing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package validatorsetsig

import (
"fmt"

"github.com/ava-labs/subnet-evm/accounts/abi"
"github.com/pkg/errors"
)

var validatorSetSigMessageType abi.Type

func init() {
// Create an ABI binding for ValidatorSetSigMessage, defined in ValidatorSetSig.sol
// abigen does not support ABI bindings for standalone structs, only methods and events,
// so we must manually keep this up-to-date with the struct defined in the contract.
var err error
validatorSetSigMessageType, err = abi.NewType("tuple", "struct Overloader.F", []abi.ArgumentMarshaling{
{Name: "targetBlockchainID", Type: "bytes32"},
{Name: "validatorSetSigAddress", Type: "address"},
{Name: "targetContractAddress", Type: "address"},
{Name: "nonce", Type: "uint256"},
{Name: "payload", Type: "bytes"},
})
if err != nil {
panic(fmt.Sprintf("failed to create ValidatorSetSigMessage ABI type: %v", err))
}
}

func PackValidatorSetSigWarpPayload(message ValidatorSetSigMessage) ([]byte, error) {
args := abi.Arguments{
{
Name: "validatorSetSigMessage",
Type: validatorSetSigMessageType,
},
}
return args.Pack(message)
}

func UnpackValidatorSetSigWarpPayload(messageBytes []byte) (ValidatorSetSigMessage, error) {
args := abi.Arguments{
{
Name: "validatorSetSigMessage",
Type: validatorSetSigMessageType,
},
}
unpacked, err := args.Unpack(messageBytes)
fmt.Println("unpacked: ", unpacked)
if err != nil {
return ValidatorSetSigMessage{}, fmt.Errorf("failed to unpack to ValidatorSetSigMessage with err: %v", err)
}
type validatorSetSigWarpPayload struct {
ValidatorSetSigMessage ValidatorSetSigMessage `json:"validatorSetSigMessage"`
}
var payload validatorSetSigWarpPayload
err = args.Copy(&payload, unpacked)
if err != nil {
return ValidatorSetSigMessage{}, err
}
return payload.ValidatorSetSigMessage, nil
}

// PackExecuteCall packs the input to form a call to the executeCall function
func PackExecuteCall(messageIndex uint32) ([]byte, error) {
abi, err := ValidatorSetSigMetaData.GetAbi()
if err != nil {
return nil, errors.Wrap(err, "failed to get abi")
}
return abi.Pack("executeCall", messageIndex)
}
31 changes: 31 additions & 0 deletions abi-bindings/go/Utilities/ValidatorSetSig/packing_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package validatorsetsig

import (
"math/big"
"testing"

"github.com/ava-labs/avalanchego/ids"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)

func TestPackUnpackValidatorSetSigMessage(t *testing.T) {
msg := ValidatorSetSigMessage{
TargetBlockchainID: ids.ID{1, 2, 3, 4},
ValidatorSetSigAddress: common.HexToAddress("0x0123456789abcdef0123456789abcdef01234567"),
TargetContractAddress: common.HexToAddress("0x0123456789abcdef0123456789abcdef01234568"),
Nonce: big.NewInt(1),
Payload: []byte{1, 2, 3, 4},
}
b, err := PackValidatorSetSigWarpPayload(msg)
require.NoError(t, err)

unpackedMsg, err := UnpackValidatorSetSigWarpPayload(b)
require.NoError(t, err)

require.Equal(t, msg, unpackedMsg)

}
7 changes: 4 additions & 3 deletions contracts/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
## Teleporter Messenger Contract and Example Cross Chain Applications
This directory contains Solidity contracts implementing the Teleporter messaging protocol and example cross-chain applications built using Teleporter.
## Teleporter Messenger Contract
This directory contains Solidity contracts implementing the Teleporter messaging protocol.

This directory is set up as a [Foundry](https://github.com/foundry-rs/foundry) project. Follow the linked guide to install the necessary dependencies. Further documentation about given contracts can be found in `src/Teleporter/` and `src/CrossChainApplications`.
This directory is set up as a [Foundry](https://github.com/foundry-rs/foundry) project. Use the `scripts/install_foundry.sh` to install the correct version of the ava-labs fork of foundry. Further documentation about given contracts can be found in`src/Teleporter/`.

## Building and Running
- To compile the contracts run `forge build` from this directory.
Expand All @@ -13,3 +13,4 @@ This directory is set up as a [Foundry](https://github.com/foundry-rs/foundry) p

## Generate documentation
- Documentation can be generated by running `forge doc --build` from this repository. By default, this will generate documentation to `contracts/docs/`, and an HTML book to `contracts/docs/book/`. It's also possible to serve this book locally by running `forge doc --serve <PORT>`.

18 changes: 18 additions & 0 deletions contracts/src/Utilities/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## Utility Contracts

### ValidatorSetSig Contract

This contract provides an alternative to traditional multi-signature contracts where instead of requiring signatures from `K` of `N` pre-specified signers, an aggregate signature is required from a quorum of the current validators for a given blockchain.

The contract leverages off-chain [Avalanche Warp Messages](https://docs.avax.network/build/cross-chain/awm/overview), which are manually approved for signing by a chain's validators. It requires these messages to have the source address set to the zero address to enforce this, since on-chain warp messages cannot have zero source address.

Note:
1. The blockchain validating the message may or may not be the same chain where the target contract and the `ValidatorSetSig` contract are deployed.
2. [Off-Chain Warp messages](https://github.com/ava-labs/subnet-evm/issues/729) are Warp messages that validators can include in their config to indicate that they are willing to sign them even though they are not a result of on-chain activity.

#### Creating a valid Off-chain Warp Message for interaction with the ValidatorSetSig Contract

1. ABI-encode a `ValidatorSetSigMessage` as defined in `ValidatorSetSig.sol`
2. OPTIONAL: call `validateMessage` view method of the intended contract to confirm that the message has correct fields, including the nonce.
3. Pack that as the payload of an [AddressedCall](https://github.com/ava-labs/avalanchego/blob/0c4efd743e1d737f4e8970d0e0ebf229ea44406c/vms/platformvm/warp/payload/addressed_call.go#L15) Warp Message format. Note that the `SourceAddress` field has to be set to the zero address.
4. Pack the `AddressedCall` as the payload of the [UnsignedWarpMessage](https://github.com/ava-labs/avalanchego/blob/f17ea6a7ab4036c41b693e47b94d8f0c81cb69ec/vms/platformvm/warp/unsigned_message.go#L14).
137 changes: 137 additions & 0 deletions contracts/src/Utilities/ValidatorSetSig.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

import {
WarpMessage,
IWarpMessenger
} from "@avalabs/[email protected]/contracts/interfaces/IWarpMessenger.sol";
import {ReentrancyGuard} from "@openzeppelin/[email protected]/security/ReentrancyGuard.sol";

/**
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/

/**
* @dev Message format for the WarpMessage payload to be forwarded to the target contract
*
* targetBlockchainID: Blockchain ID of the chain the message is intended for
* validatorSetSigAddress: Address of the ValidatorSetSig contract this message is intended for
* targetContractAddress: Address of the contract that the payload should be forwarded to
* nonce: Unique nonce for the target contract address to provide replay protection
* payload: Payload to be forwarded to the target contract. Usually ABI encoded function call with parameters.
*/
struct ValidatorSetSigMessage {
bytes32 targetBlockchainID;
address validatorSetSigAddress;
address targetContractAddress;
uint256 nonce;
bytes payload;
}

/**
* @dev Contract that verifies that a set threshold of validators from a given blockchainID
* have signed an off-chain Warp message and forwards the payload to the target contract specified in the ValidatorSetSigMessage.
* The threshold itself is set by the validator themselves in their Warp configs:
* https://github.com/ava-labs/subnet-evm/blob/6c018f89339f3d381909e02013f002f234dc7ae3/precompile/contracts/warp/config.go#L50
*
* This is intended to be used for safe off-chain governance of enabled contracts. An example use case would be
* to deploy an `Ownable` target contract that is owned by an instance of this contract and adding the
* `onlyOwner` modifier to the functions that should be governed.
*
* @custom:security-contact https://github.com/ava-labs/teleporter/blob/main/SECURITY.md
*/
contract ValidatorSetSig is ReentrancyGuard {
/**
* @notice A blockchain ID whose validators need to sign the message for it to be considered valid.
*/
bytes32 public immutable validatorBlockchainID;

/**
* @dev Tracks nonces for messages sent to a specific contract address to provide replay protection.
* The target contract address is used as the key instead of keeping a global nonce to prevent
* one party from consuming a global nonce that another was intending to use
* in case a single instance of ValidatorSetSig contract is used to manage multiple downstream contracts.
*/
mapping(address targetContractAddress => uint256 nonce) public nonces;

/**
* @notice Address that the off-chain Warp message sets as the "source" address.
* @dev The address is not owned by any EOA or smart contract account, so it
* cannot possibly be the source address of any other Warp message emitted by the VM.
*/
address public constant VALIDATORS_SOURCE_ADDRESS = address(0);

/**
* @notice The blockchain ID of the chain the contract is deployed on.
*/
bytes32 public immutable blockchainID;

/**
* @notice Warp precompile used for sending and receiving Warp messages.
*/
IWarpMessenger public constant WARP_MESSENGER =
IWarpMessenger(0x0200000000000000000000000000000000000005);

/**
* @notice Emited when the payload is successfully delivered to the target contract.
*/
event Delivered(address indexed targetContractAddress, uint256 indexed nonce);

constructor(bytes32 validatorBlockchainID_) {
validatorBlockchainID = validatorBlockchainID_;
blockchainID = WARP_MESSENGER.getBlockchainID();
}

function executeCall(uint32 messageIndex) external nonReentrant {
// Get the WarpMessage from the WarpMessenger precompile and verify that it is valid
(WarpMessage memory message, bool valid) =
WARP_MESSENGER.getVerifiedWarpMessage(messageIndex);
require(valid, "ValidatorSetSig: invalid warp message");

// Ensure that the sourceChainID of the validator set that signed the message is the authorized one
require(
message.sourceChainID == validatorBlockchainID, "ValidatorSetSig: invalid sourceChainID"
);
// Ensure that the originSenderAddress is the zero address to prevent on-chain messages from being forwarded
require(
message.originSenderAddress == VALIDATORS_SOURCE_ADDRESS,
"ValidatorSetSig: non-zero originSenderAddress"
);

ValidatorSetSigMessage memory validatorSetSigMessage =
abi.decode(message.payload, (ValidatorSetSigMessage));

validateMessage(validatorSetSigMessage);

nonces[validatorSetSigMessage.targetContractAddress] = validatorSetSigMessage.nonce + 1;

// We don't need to protect against return bomb vectors below here since the caller is expected to have full control over the contract called.
(bool success,) =
// solhint-disable-next-line avoid-low-level-calls
validatorSetSigMessage.targetContractAddress.call(validatorSetSigMessage.payload);

// Use require to revert the transaction if the call fails. This is to prevent consuming the nonce if the call fails due to out of gas
// and requiring re-signing of the message with a new nonce.
require(success, "ValidatorSetSig: call failed");
emit Delivered(validatorSetSigMessage.targetContractAddress, validatorSetSigMessage.nonce);
}

function validateMessage(ValidatorSetSigMessage memory message) public view {
require(
message.targetBlockchainID == blockchainID,
"ValidatorSetSig: invalid targetBlockchainID"
);
require(
message.validatorSetSigAddress == address(this),
"ValidatorSetSig: invalid validatorSetSigAddress"
);
require(
nonces[message.targetContractAddress] == message.nonce, "ValidatorSetSig: invalid nonce"
);
}
}
Loading

0 comments on commit 6de0f52

Please sign in to comment.