change semantics of contract overrides again: any direct/indirect dependencies of contracts/libraries specified with override addresses should not be deployed if they are not elsewhere referenced as direct/indirect deps of non-override contracts.

This commit is contained in:
Jared Wasinger 2024-12-13 15:39:44 +07:00 committed by Felix Lange
parent 736d587990
commit 47192a758c
4 changed files with 59 additions and 74 deletions

View File

@ -194,6 +194,8 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri
return c.abi.UnpackIntoInterface(res[0], method, output)
}
// CallRaw executes an eth_call against the contract with the raw calldata as
// input. It returns the call's return data or an error.
func (c *BoundContract) CallRaw(opts *CallOpts, input []byte) ([]byte, error) {
return c.call(opts, input)
}

View File

@ -311,10 +311,10 @@ func bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
events[original.Name] = &tmplEvent{Original: original, Normalized: normalized}
}
for _, original := range evmABI.Errors {
// TODO: I copied this from events. I think it should be correct but not totally sure
// TODO: I copied this from events (above in this function). I think it should be correct but not totally sure
// even if it is correct, should consider deduplicating this into its own function.
// Normalize the event for capital cases and non-anonymous outputs
// Normalize the error for capital cases and non-anonymous outputs
normalized := original
// Ensure there is no duplicated identifier

View File

@ -241,28 +241,29 @@ func TestContractLinking(t *testing.T) {
map[rune]struct{}{'a': {}},
map[rune]struct{}{}})
// two contracts share some dependencies. one contract is marked as an override. only the override contract
// is not deployed. its dependencies are all deployed (even the ones not used by f), and the ones shared with f
// are not redeployed
// two contracts ('a' and 'f') share some dependencies. contract 'a' is marked as an override. expect that any of
// its depdencies that aren't shared with 'f' are not deployed.
testLinkCase(t, linkTestCaseInput{map[rune][]rune{
'a': {'b', 'c', 'd', 'e'},
'f': {'g', 'c', 'd', 'h'}},
map[rune]struct{}{'a': {}},
map[rune]struct{}{
'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {},
'f': {}, 'g': {}, 'c': {}, 'd': {}, 'h': {},
}})
// test nested libraries that share deps at different levels of the tree... with override.
// same condition as above test: no sub-dependencies of
testLinkCase(t, linkTestCaseInput{
map[rune][]rune{
'a': {'b', 'c', 'd', 'e'},
'e': {'f', 'g', 'h', 'i', 'm'},
'i': {'j', 'k', 'l', 'm'}},
'i': {'j', 'k', 'l', 'm'},
'l': {'n', 'o', 'p'}},
map[rune]struct{}{
'i': {},
},
map[rune]struct{}{
'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {}, 'j': {}, 'k': {}, 'l': {}, 'm': {},
'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {}, 'm': {},
}})
// TODO: same as the above case but nested one level of dependencies deep (?)
}

View File

@ -65,12 +65,14 @@ func (d *DeploymentResult) Accumulate(other *DeploymentResult) {
}
}
// depTreeBuilder turns a set of unlinked contracts and their dependent libraries into a collection of trees
// representing the relation of their dependencies.
type depTreeBuilder struct {
overrides map[string]common.Address
// map of pattern to unlinked contract bytecode (for libraries or contracts)
contracts map[string]string
// map of pattern to subtree represented by contract
subtrees map[string]any
subtrees map[string]*depTreeNode
// map of nodes that aren't referenced by other dependencies (these can be libraries too if user is doing lib-only deployment)
roots map[string]struct{}
}
@ -78,12 +80,8 @@ type depTreeBuilder struct {
type depTreeNode struct {
pattern string
unlinkedCode string
nodes []any
}
type overrideNode struct {
pattern string
addr common.Address
nodes []*depTreeNode
overrideAddr *common.Address
}
func (d *depTreeBuilder) buildDepTrees(pattern, contract string) {
@ -91,19 +89,15 @@ func (d *depTreeBuilder) buildDepTrees(pattern, contract string) {
if _, ok := d.subtrees[pattern]; ok {
return
}
if addr, ok := d.overrides[pattern]; ok {
node := &overrideNode{
pattern: pattern,
addr: addr,
}
d.subtrees[pattern] = node
} else {
node := &depTreeNode{
pattern: pattern,
unlinkedCode: contract,
}
// if the node is an override node: add it to the node map but don't recurse on it.
// else if the node is depNode: recurse on it, and add it to the dep map.
if addr, ok := d.overrides[pattern]; ok {
node.overrideAddr = &addr
}
reMatchSpecificPattern, err := regexp.Compile("__\\$([a-f0-9]+)\\$__")
if err != nil {
panic(err)
@ -117,24 +111,27 @@ func (d *depTreeBuilder) buildDepTrees(pattern, contract string) {
delete(d.roots, depPattern)
}
d.subtrees[pattern] = node
}
}
func (d *depTreeBuilder) BuildDepTrees() (roots []*depTreeNode) {
for pattern, contract := range d.contracts {
// before the trees of dependencies are known, consider that any provided contract could be a root.
for pattern, _ := range d.contracts {
d.roots[pattern] = struct{}{}
}
// recursively build each part of the dependency subtree by starting at
for pattern, contract := range d.contracts {
d.buildDepTrees(pattern, contract)
}
for pattern, _ := range d.roots {
switch node := d.subtrees[pattern].(type) {
case *depTreeNode:
roots = append(roots, node)
}
roots = append(roots, d.subtrees[pattern])
}
return roots
}
type treeDeployer struct {
// depTreeDeployer is responsible for taking a built dependency, deploying-and-linking its components in the proper
// order.
type depTreeDeployer struct {
deployedAddrs map[string]common.Address
deployerTxs map[string]*types.Transaction
input map[string][]byte // map of the root contract pattern to the constructor input (if there is any)
@ -142,10 +139,9 @@ type treeDeployer struct {
err error
}
func (d *treeDeployer) linkAndDeploy(n any) {
node, ok := n.(*depTreeNode)
if !ok {
// this was an override node
func (d *depTreeDeployer) linkAndDeploy(node *depTreeNode) {
if node.overrideAddr != nil {
// don't recurse on override nodes
return
}
@ -154,16 +150,14 @@ func (d *treeDeployer) linkAndDeploy(n any) {
}
// link in all node dependencies and produce the deployer bytecode
deployerCode := node.unlinkedCode
for _, c := range node.nodes {
switch child := c.(type) {
case *depTreeNode:
deployerCode = strings.ReplaceAll(deployerCode, "__$"+child.pattern+"$__", strings.ToLower(d.deployedAddrs[child.pattern].String()[2:]))
case *overrideNode:
deployerCode = strings.ReplaceAll(deployerCode, "__$"+child.pattern+"$__", strings.ToLower(child.addr.String()[2:]))
default:
panic("invalid node type")
for _, child := range node.nodes {
var linkAddr common.Address
if child.overrideAddr != nil {
linkAddr = *child.overrideAddr
} else {
linkAddr = d.deployedAddrs[child.pattern]
}
deployerCode = strings.ReplaceAll(deployerCode, "__$"+child.pattern+"$__", strings.ToLower(linkAddr.String()[2:]))
}
// deploy the contract.
@ -176,7 +170,7 @@ func (d *treeDeployer) linkAndDeploy(n any) {
}
}
func (d *treeDeployer) Result() (*DeploymentResult, error) {
func (d *depTreeDeployer) Result() (*DeploymentResult, error) {
if d.err != nil {
return nil, d.err
}
@ -202,13 +196,13 @@ func LinkAndDeploy(deployParams DeploymentParams, deploy func(input, deployer []
treeBuilder := depTreeBuilder{
overrides: deployParams.Overrides,
contracts: unlinkedContracts,
subtrees: make(map[string]any),
subtrees: make(map[string]*depTreeNode),
roots: make(map[string]struct{}),
}
deps := treeBuilder.BuildDepTrees()
for _, tr := range deps {
deployer := treeDeployer{
deployer := depTreeDeployer{
deploy: deploy,
deployedAddrs: make(map[string]common.Address),
deployerTxs: make(map[string]*types.Transaction)}
@ -373,15 +367,3 @@ func Call[T any](instance *ContractInstance, opts *bind.CallOpts, packedInput []
}
return unpack(packedOutput)
}
/*
func UnpackError(metadata *bind.MetaData, raw []byte) (any, error) {
fmt.Printf("raw is %x\n", raw[0:4])
errType := metadata.Errors[fmt.Sprintf("%x", raw[0:4])]
abi, _ := metadata.GetAbi() // TODO: check error here?
res := reflect.New(errType).Interface()
fmt.Printf("err type name is %s\n", errType.Name())
err := abi.UnpackIntoInterface(&res, errType.Name(), raw)
return res, err
}
*/