-
Notifications
You must be signed in to change notification settings - Fork 59
/
Copy pathcompiled_contract.go
160 lines (139 loc) · 6.09 KB
/
compiled_contract.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package types
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi"
"golang.org/x/exp/slices"
"strings"
)
// LinkInfo provides the library name and placeholder string to replace in the unlinked bytecode.
type LinkInfo struct {
Name string
Placeholder string
}
// CompiledContract represents a single contract unit from a smart contract compilation.
type CompiledContract struct {
// Abi describes a contract's application binary interface, a structure used to describe information needed
// to interact with the contract such as constructor and function definitions with input/output variable
// information, event declarations, and fallback and receive methods.
Abi abi.ABI
// InitBytecode is the raw (potentially unlinked) init bytecode.
UnlinkedInitBytecode string
// InitBytecode is the hex-decoded bytecode used to deploy a contract.
InitBytecode []byte
// InitBytecode is the raw (potentially unlinked) runtime bytecode.
UnlinkedRuntimeBytecode string
// RuntimeBytecode is the bytecode expected to exist at the deployed address after construction.
// This may differ at runtime based on constructor arguments, immutables, etc.
RuntimeBytecode []byte
// SrcMapsInit describes the source mappings to associate source file and bytecode segments in InitBytecode.
SrcMapsInit string
// SrcMapsRuntime describes the source mappings to associate source file and bytecode segments in RuntimeBytecode.
SrcMapsRuntime string
// LibraryDependencies lists the resolved library dependencies for this contract in post-order.
// They must be deployed in this order to handle libraries which depend on other libraries.
LibraryDependencies []LinkInfo
}
// Decode our init and runtime bytecode from string to hex. This should be called after library linking.
func (c *CompiledContract) DecodeFullyLinkedBytecode() error {
var err error
c.InitBytecode, err = hex.DecodeString(strings.TrimPrefix(c.UnlinkedInitBytecode, "0x"))
if err != nil {
return fmt.Errorf("unable to parse init bytecode for contract. Are all libraries linked?\n")
}
c.RuntimeBytecode, err = hex.DecodeString(strings.TrimPrefix(c.UnlinkedRuntimeBytecode, "0x"))
if err != nil {
return fmt.Errorf("unable to parse runtime bytecode for contract\n Are all libraries linked?\n")
}
return nil
}
// IsMatch returns a boolean indicating whether provided contract bytecode is a match to this compiled contract
// definition.
func (c *CompiledContract) IsMatch(initBytecode []byte, runtimeBytecode []byte) bool {
// Check if we can compare init and runtime bytecode
canCompareInit := len(initBytecode) > 0 && len(c.InitBytecode) > 0
canCompareRuntime := len(runtimeBytecode) > 0 && len(c.RuntimeBytecode) > 0
// First try matching runtime bytecode contract metadata.
if canCompareRuntime {
// First we try to match contracts with contract metadata embedded within the smart contract.
// Note: We use runtime bytecode for this because init byte code can have matching metadata hashes for different
// contracts.
deploymentMetadata := ExtractContractMetadata(runtimeBytecode)
definitionMetadata := ExtractContractMetadata(c.RuntimeBytecode)
if deploymentMetadata != nil && definitionMetadata != nil {
deploymentBytecodeHash := deploymentMetadata.ExtractBytecodeHash()
definitionBytecodeHash := definitionMetadata.ExtractBytecodeHash()
if deploymentBytecodeHash != nil && definitionBytecodeHash != nil {
return bytes.Equal(deploymentBytecodeHash, definitionBytecodeHash)
}
}
}
// Since we could not match with runtime bytecode's metadata hashes, we try to match based on init code. To do this,
// we anticipate our init bytecode might contain appended arguments, so we'll be slicing it down to size and trying
// to match as a last ditch effort.
if canCompareInit {
// If the init byte code size is larger than what we initialized with, it is not a match.
if len(c.InitBytecode) > len(initBytecode) {
return false
}
// Cut down the contract init bytecode to the size of the definition's to attempt to strip away constructor
// arguments before performing a direct compare.
cutDeployedInitBytecode := initBytecode[:len(c.InitBytecode)]
// If the byte code matches exactly, we treat this as a match.
if bytes.Equal(cutDeployedInitBytecode, c.InitBytecode) {
return true
}
}
// As a final fallback, try to compare the whole runtime byte code (least likely to work, given the deployment
// process, e.g. smart contract constructor, will change the runtime code in most cases).
if canCompareRuntime {
// If the byte code matches exactly, we treat this as a match.
if bytes.Equal(runtimeBytecode, c.RuntimeBytecode) {
return true
}
}
// Otherwise return our failed match status.
return false
}
// ParseABIFromInterface parses a generic object into an abi.ABI and returns it, or an error if one occurs.
func ParseABIFromInterface(i any) (*abi.ABI, error) {
var (
result abi.ABI
err error
)
// If it's a string, just parse it. Otherwise, we assume it's an interface and serialize it into a string.
if s, ok := i.(string); ok {
result, err = abi.JSON(strings.NewReader(s))
if err != nil {
return nil, err
}
} else {
var b []byte
b, err = json.Marshal(i)
if err != nil {
return nil, err
}
result, err = abi.JSON(strings.NewReader(string(b)))
if err != nil {
return nil, err
}
}
return &result, nil
}
// GetDeploymentMessageData is a helper method used create contract deployment message data for the given contract.
// This data can be set in transaction/message structs "data" field to indicate the packed init bytecode and constructor
// argument data to use.
func (c *CompiledContract) GetDeploymentMessageData(args []any) ([]byte, error) {
// ABI encode constructor arguments and append them to the end of the bytecode
initBytecodeWithArgs := slices.Clone(c.InitBytecode)
if len(c.Abi.Constructor.Inputs) > 0 {
data, err := c.Abi.Pack("", args...)
if err != nil {
return nil, fmt.Errorf("could not encode constructor arguments due to error: %v", err)
}
initBytecodeWithArgs = append(initBytecodeWithArgs, data...)
}
return initBytecodeWithArgs, nil
}