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

call 'visibilityRules' method #2058

Merged
merged 3 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions contracts/src/lib/ContractTransparencyConfig.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.20;

// implement this interface if you want to configure the visibility rules of your smart contract
// the TEN platform will interpret this information
interface ContractTransparencyConfig {
// configuration per event log type
struct EventLogConfig {
bytes eventSignature;
bool isPublic; // everyone can see and query for this event
bool topic1CanView; // If the event is private, and this is true, it means that the address from topic1 is an EOA that can view this event
bool topic2CanView; // same
bool topic3CanView; // same
bool visibleToSender; // if true, the tx signer will see this event. Default false
}

struct VisibilityConfig {
bool isTransparent; // If true - the internal state via getStorageAt will be accessible to everyone. All events will be public. Default false
EventLogConfig[] eventLogConfigs; // mapping from event signature to visibility configs per event
}

// keep the logic independent of the environment
// max gas: 1 Million
function visibilityRules() external pure returns (VisibilityConfig memory);
}
93 changes: 93 additions & 0 deletions go/enclave/evm/ContractTransparency.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package evm

import (
"errors"
"math/big"
"strings"

ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
)

// file generated by ethereum tooling to help calling the "visibilityRules" function

// Reference imports to suppress errors if they are not otherwise used.
var (
_ = errors.New
_ = big.NewInt
_ = strings.NewReader
_ = ethereum.NotFound
_ = bind.Bind
_ = common.Big1
_ = types.BloomLookup
_ = event.NewSubscription
_ = abi.ConvertType
)

// ContractTranspMetaData contains all meta data concerning the TransparencyConfig contract.
var ContractTranspMetaData = &bind.MetaData{
ABI: "[{\"inputs\":[],\"name\":\"visibilityRules\",\"outputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"isTransparent\",\"type\":\"bool\"},{\"components\":[{\"internalType\":\"bytes\",\"name\":\"eventSignature\",\"type\":\"bytes\"},{\"internalType\":\"bool\",\"name\":\"isPublic\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"topic1CanView\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"topic2CanView\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"topic3CanView\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"visibleToSender\",\"type\":\"bool\"}],\"internalType\":\"structContractTransparencyConfig.EventLogConfig[]\",\"name\":\"eventLogConfigs\",\"type\":\"tuple[]\"}],\"internalType\":\"structContractTransparencyConfig.VisibilityConfig\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]",
}

// ContractTransparencyConfigEventLogConfig is an auto generated low-level Go binding around an user-defined struct.
type ContractTransparencyConfigEventLogConfig struct {
EventSignature []byte
IsPublic bool
Topic1CanView bool
Topic2CanView bool
Topic3CanView bool
VisibleToSender bool
}

// ContractTransparencyConfigVisibilityConfig is an auto generated low-level Go binding around an user-defined struct.
type ContractTransparencyConfigVisibilityConfig struct {
IsTransparent bool
EventLogConfigs []ContractTransparencyConfigEventLogConfig
}

// TransparencyConfig is an auto generated Go binding around an Ethereum contract.
type TransparencyConfig struct {
TransparencyConfigCaller // Read-only binding to the contract
}

// TransparencyConfigCaller is an auto generated read-only Go binding around an Ethereum contract.
type TransparencyConfigCaller struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}

// NewTransparencyConfigCaller creates a new read-only instance of TransparencyConfig, bound to a specific deployed contract.
func NewTransparencyConfigCaller(address common.Address, caller bind.ContractCaller) (*TransparencyConfigCaller, error) {
contract, err := bindContractTransp(address, caller, nil, nil)
if err != nil {
return nil, err
}
return &TransparencyConfigCaller{contract: contract}, nil
}

// bindContractTransp binds a generic wrapper to an already deployed contract.
func bindContractTransp(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
parsed, err := ContractTranspMetaData.GetAbi()
if err != nil {
return nil, err
}
return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil
}

// VisibilityRules is a free data retrieval call binding the contract method 0x30173dd1.
//
// Solidity: function visibilityRules() pure returns((bool,(bytes,bool,bool,bool,bool,bool)[]))
func (_ContractTransp *TransparencyConfigCaller) VisibilityRules(opts *bind.CallOpts) (ContractTransparencyConfigVisibilityConfig, error) {
var out []interface{}
err := _ContractTransp.contract.Call(opts, &out, "visibilityRules")
if err != nil {
return *new(ContractTransparencyConfigVisibilityConfig), err
}

out0 := *abi.ConvertType(out[0], new(ContractTransparencyConfigVisibilityConfig)).(*ContractTransparencyConfigVisibilityConfig)

return out0, err
}
66 changes: 62 additions & 4 deletions go/enclave/evm/evm_facade.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"math/big"
_ "unsafe"

"github.com/ethereum/go-ethereum"

"github.com/ethereum/go-ethereum/core/tracing"
"github.com/holiman/uint256"

Expand Down Expand Up @@ -153,6 +155,7 @@ func executeTransaction(
// calculate a random value per transaction
header.MixDigest = crypto.CalculateTxRnd(before.Bytes(), tCount)

var vmenv *vm.EVM
applyTx := func(
config *params.ChainConfig,
bc gethcore.ChainContext,
Expand Down Expand Up @@ -196,7 +199,7 @@ func executeTransaction(

// Create a new context to be used in the EVM environment
blockContext := gethcore.NewEVMBlockContext(header, bc, author)
vmenv := vm.NewEVM(blockContext, vm.TxContext{BlobHashes: tx.Tx.BlobHashes(), GasPrice: header.BaseFee}, statedb, config, cfg)
vmenv = vm.NewEVM(blockContext, vm.TxContext{BlobHashes: tx.Tx.BlobHashes(), GasPrice: header.BaseFee}, statedb, config, cfg)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not really sure why this applyTx nested function exists, makes this quite hard to follow. But seems like vmenv will always be created or we'd have returned with error before you use it outside the function so guess it works.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

tbh, I don't remember either. I think @StefanIliev545 might have worked in this area as well.
At some point we were depending on geth internal functions, which eventually became public

var receipt *types.Receipt
receipt, err = gethcore.ApplyTransactionWithEVM(msg, config, gp, statedb, header.Number, header.Hash(), tx.Tx, usedGas, vmenv)
if err != nil {
Expand Down Expand Up @@ -241,16 +244,55 @@ func executeTransaction(
return &core.TxExecResult{Receipt: receipt, Err: err}
}

// todo - placeholder for calling the visibility config function on the newly created contracts
// this is step 1 of the transition to configured visibility rules. The auto-detection of the visibility rules
contractsWithVisibility := make(map[gethcommon.Address]*core.ContractVisibilityConfig)
for _, contractAddress := range createdContracts {
contractsWithVisibility[*contractAddress] = &core.ContractVisibilityConfig{AutoConfig: true}
contractsWithVisibility[*contractAddress] = readVisibilityConfig(vmenv, contractAddress)
}

return &core.TxExecResult{Receipt: receipt, CreatedContracts: contractsWithVisibility}
}

const (
maxGasForVisibility = 30_000 // hardcode at 30k gas.
)

func readVisibilityConfig(vmenv *vm.EVM, contractAddress *gethcommon.Address) *core.ContractVisibilityConfig {
cc, err := NewTransparencyConfigCaller(*contractAddress, &localContractCaller{evm: vmenv, maxGasForVisibility: maxGasForVisibility})
if err != nil {
// unrecoverable error. should not happen
panic(fmt.Sprintf("could not create transparency config caller. %v", err))
}
visibilityRules, err := cc.VisibilityRules(nil)
if err != nil {
// there is no visibility defined, so we return auto
return &core.ContractVisibilityConfig{AutoConfig: true}
}

cfg := &core.ContractVisibilityConfig{
AutoConfig: false,
Transparent: &visibilityRules.IsTransparent,
EventConfigs: make(map[gethcommon.Hash]*core.EventVisibilityConfig),
}

for i := range visibilityRules.EventLogConfigs {
logConfig := visibilityRules.EventLogConfigs[i]

sig := gethcommon.Hash{}
sig.SetBytes(logConfig.EventSignature)

cfg.EventConfigs[sig] = &core.EventVisibilityConfig{
AutoConfig: false,
Public: logConfig.IsPublic,
Topic1CanView: &logConfig.Topic1CanView,
Topic2CanView: &logConfig.Topic2CanView,
Topic3CanView: &logConfig.Topic3CanView,
SenderCanView: &logConfig.VisibleToSender,
}
}

return cfg
}

func logReceipt(r *types.Receipt, logger gethlog.Logger) {
if logger.Enabled(context.Background(), gethlog.LevelTrace) {
logger.Trace("Receipt", log.TxKey, r.TxHash, "Result", receiptToString(r))
Expand Down Expand Up @@ -367,3 +409,19 @@ func newRevertError(result *gethcore.ExecutionResult) error {
Code: 3, // todo - magic number, really needs thought around the value and made a constant
}
}

// used as a wrapper around the vm.EVM to allow for easier calling of smart contract view functions
type localContractCaller struct {
evm *vm.EVM
maxGasForVisibility uint64
}

// CodeAt - not implemented because it's not needed for our use case. It just has to return something non-nil
func (cc *localContractCaller) CodeAt(_ context.Context, _ gethcommon.Address, _ *big.Int) ([]byte, error) {
return []byte{0}, nil
}

func (cc *localContractCaller) CallContract(_ context.Context, call ethereum.CallMsg, _ *big.Int) ([]byte, error) {
ret, _, err := cc.evm.Call(vm.AccountRef(call.From), *call.To, call.Data, cc.maxGasForVisibility, uint256.NewInt(0))
return ret, err
}
9 changes: 6 additions & 3 deletions go/enclave/storage/events_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,8 @@ func (es *eventsStorage) storeTopics(ctx context.Context, dbTX *sql.Tx, eventTyp
}

// this function contains visibility logic
func (es *eventsStorage) storeTopic(ctx context.Context, dbTX *sql.Tx, eventType *enclavedb.EventType, i int, topic gethcommon.Hash) (uint64, error) {
relevantAddress, err := es.determineRelevantAddressForTopic(ctx, dbTX, eventType, i, topic)
func (es *eventsStorage) storeTopic(ctx context.Context, dbTX *sql.Tx, eventType *enclavedb.EventType, topicNo int, topic gethcommon.Hash) (uint64, error) {
relevantAddress, err := es.determineRelevantAddressForTopic(ctx, dbTX, eventType, topicNo, topic)
if err != nil && !errors.Is(err, errutil.ErrNotFound) {
return 0, fmt.Errorf("could not determine visibility rules. cause: %w", err)
}
Expand Down Expand Up @@ -275,8 +275,11 @@ func (es *eventsStorage) determineRelevantAddressForTopic(ctx context.Context, d

case eventType.IsTopicRelevant(topicNumber):
relevantAddress = common.ExtractPotentialAddress(topic)
// it is possible for contracts to emit events without an actual address.
// for example. ERC20.mint emits a transfer event from a "0" address
if relevantAddress == nil {
return nil, fmt.Errorf("invalid configuration. expected address in topic %d : %s", topicNumber, topic.String())
es.logger.Debug(fmt.Sprintf("invalid configuration. expected address in topic %d : %s", topicNumber, topic.String()))
return nil, errutil.ErrNotFound
}

case !eventType.IsTopicRelevant(topicNumber):
Expand Down
56 changes: 44 additions & 12 deletions integration/erc20contract/ObsERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,63 @@ pragma solidity ^0.8.4;
import "libs/openzeppelin/contracts/token/ERC20/ERC20.sol";
//import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// todo - can't import from the /contracts folder
// implement this interface if you want to configure the visibility rules of your smart contract
// the TEN platform will interpret this information
interface ContractTransparencyConfig {
// configuration per event log type
struct EventLogConfig {
bytes eventSignature;
bool isPublic; // everyone can see and query for this event
bool topic1CanView; // If the event is private, and this is true, it means that the address from topic1 is an EOA that can view this event
bool topic2CanView; // same
bool topic3CanView; // same
bool visibleToSender; // if true, the tx signer will see this event. Default false
}

struct VisibilityConfig {
bool isTransparent; // If true - the internal state via getStorageAt will be accessible to everyone. All events will be public. Default false
EventLogConfig[] eventLogConfigs; // mapping from event signature to visibility configs per event
}

// keep the logic independent of the environment
// max gas: 1 Million
function visibilityRules() external pure returns (VisibilityConfig memory);
}

interface Structs {
struct CrossChainMessage {
address sender;
uint64 sequence;
uint32 nonce;
bytes topic;
bytes payload;
uint8 consistencyLevel;
uint64 sequence;
uint32 nonce;
bytes topic;
bytes payload;
uint8 consistencyLevel;
}
}

interface IMessageBus {
function publishMessage(
uint32 nonce,
uint32 topic,
bytes calldata payload,
bytes calldata payload,
uint8 consistencyLevel
) external payable returns (uint64 sequence);
}

// This is an implementation of a canonical ERC20 as used in the Obscuro network
// where access to data has to be restricted.
contract ObsERC20 is ERC20 {
contract ObsERC20 is ERC20, ContractTransparencyConfig {

address bridge = 0xdeB34A740ECa1eC42C8b8204CBEC0bA34FDD27f3;

IMessageBus bus;

enum Topics{
MINT,
TRANSFER
enum Topics{
MINT,
TRANSFER
}

struct AssetTransferMessage {
address sender;
address receiver;
Expand All @@ -52,14 +77,21 @@ contract ObsERC20 is ERC20 {
bus = IMessageBus(busAddress);
}

function visibilityRules() public override pure returns (VisibilityConfig memory){
EventLogConfig[] memory configs = new EventLogConfig[](1);
// erc20 transfer
configs[0] = EventLogConfig(hex"ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", false, true, true, false, false);
return VisibilityConfig(false, configs);
}

function _beforeTokenTransfer(address from, address to, uint256 amount)
internal virtual override {
//Only deposit messages.
if (address(bus) == address(0x0)) {
return;
}

if (to == bridge) {
if (to == bridge) {
AssetTransferMessage memory message = AssetTransferMessage(from, to, amount);
uint64 sequence = bus.publishMessage(uint32(block.number), uint32(Topics.TRANSFER), abi.encode(message), 0);
}
Expand Down
Loading
Loading