go-ethereum/accounts/abi/bind/dep_tree_test.go

287 lines
8.9 KiB
Go
Raw Normal View History

// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package bind
import (
"fmt"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
2024-12-06 07:45:49 -06:00
"golang.org/x/exp/rand"
)
type linkTestCase struct {
// map of pattern to unlinked bytecode (for the purposes of tests just contains the patterns of its dependencies)
libCodes map[string]string
contractCodes map[string]string
overrides map[string]common.Address
}
func makeLinkTestCase(input map[rune][]rune, overrides map[rune]common.Address) *linkTestCase {
codes := make(map[string]string)
libCodes := make(map[string]string)
contractCodes := make(map[string]string)
inputMap := make(map[rune]map[rune]struct{})
// set of solidity patterns for all contracts that are known to be libraries
libs := make(map[string]struct{})
// map of test contract id (rune) to the solidity library pattern (hash of that rune)
patternMap := map[rune]string{}
for contract, deps := range input {
inputMap[contract] = make(map[rune]struct{})
if _, ok := patternMap[contract]; !ok {
patternMap[contract] = crypto.Keccak256Hash([]byte(string(contract))).String()[2:36]
}
for _, dep := range deps {
if _, ok := patternMap[dep]; !ok {
patternMap[dep] = crypto.Keccak256Hash([]byte(string(dep))).String()[2:36]
}
codes[patternMap[contract]] = codes[patternMap[contract]] + fmt.Sprintf("__$%s$__", patternMap[dep])
inputMap[contract][dep] = struct{}{}
libs[patternMap[dep]] = struct{}{}
}
}
overridesPatterns := make(map[string]common.Address)
for contractId, overrideAddr := range overrides {
pattern := crypto.Keccak256Hash([]byte(string(contractId))).String()[2:36]
overridesPatterns[pattern] = overrideAddr
}
for _, pattern := range patternMap {
if _, ok := libs[pattern]; ok {
// if the library didn't depend on others, give it some dummy code to not bork deployment logic down-the-line
if len(codes[pattern]) == 0 {
libCodes[pattern] = "ff"
} else {
libCodes[pattern] = codes[pattern]
}
} else {
contractCodes[pattern] = codes[pattern]
}
}
return &linkTestCase{
libCodes,
contractCodes,
overridesPatterns,
}
}
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
type linkTestCaseInput struct {
input map[rune][]rune
overrides map[rune]struct{}
expectDeployed map[rune]struct{}
}
func testLinkCase(t *testing.T, tcInput linkTestCaseInput) {
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
var testAddrNonce uint64
2024-12-09 00:56:53 -06:00
overridesAddrs := make(map[common.Address]struct{})
2024-12-06 07:45:49 -06:00
// generate deterministic addresses for the override set.
rand.Seed(42)
overrideAddrs := make(map[rune]common.Address)
for contract, _ := range tcInput.overrides {
2024-12-06 07:45:49 -06:00
var addr common.Address
rand.Read(addr[:])
overrideAddrs[contract] = addr
2024-12-09 00:56:53 -06:00
overridesAddrs[addr] = struct{}{}
2024-12-06 07:45:49 -06:00
}
tc := makeLinkTestCase(tcInput.input, overrideAddrs)
allContracts := make(map[rune]struct{})
for contract, deps := range tcInput.input {
allContracts[contract] = struct{}{}
for _, dep := range deps {
allContracts[dep] = struct{}{}
}
}
mockDeploy := func(input []byte, deployer []byte) (common.Address, *types.Transaction, error) {
contractAddr := crypto.CreateAddress(testAddr, testAddrNonce)
testAddrNonce++
if len(deployer) >= 20 {
// assert that this contract only references libs that are known to be deployed or in the override set
for i := 0; i < len(deployer); i += 20 {
var dep common.Address
dep.SetBytes(deployer[i : i+20])
if _, ok := overridesAddrs[dep]; !ok {
t.Fatalf("reference to dependent contract that has not yet been deployed: %x\n", dep)
}
}
}
2024-12-09 00:56:53 -06:00
overridesAddrs[contractAddr] = struct{}{}
// we don't care about the txs themselves for the sake of the linking tests. so we can return nil for them in the mock deployer
return contractAddr, nil, nil
}
var (
2024-12-09 23:23:23 -06:00
deployParams DeploymentParams
)
for pattern, bin := range tc.contractCodes {
deployParams.Contracts = append(deployParams.Contracts, &MetaData{Pattern: pattern, Bin: "0x" + bin})
}
for pattern, bin := range tc.libCodes {
deployParams.Contracts = append(deployParams.Contracts, &MetaData{
Bin: "0x" + bin,
Pattern: pattern,
})
}
2024-12-06 07:45:49 -06:00
overridePatterns := make(map[string]common.Address)
for pattern, override := range tc.overrides {
overridePatterns[pattern] = override
}
2024-12-09 23:23:23 -06:00
deployParams.Overrides = overridePatterns
res, err := LinkAndDeploy(deployParams, mockDeploy)
if err != nil {
t.Fatalf("got error from LinkAndDeploy: %v\n", err)
}
if len(res.Addrs) != len(tcInput.expectDeployed) {
t.Fatalf("got %d deployed contracts. expected %d.\n", len(res.Addrs), len(tcInput.expectDeployed))
2024-12-09 00:56:53 -06:00
}
for contract, _ := range tcInput.expectDeployed {
2024-12-09 00:56:53 -06:00
pattern := crypto.Keccak256Hash([]byte(string(contract))).String()[2:36]
if _, ok := res.Addrs[pattern]; !ok {
t.Fatalf("expected contract %s was not deployed\n", string(contract))
}
}
}
func TestContractLinking(t *testing.T) {
testLinkCase(t, linkTestCaseInput{
map[rune][]rune{
'a': {'b', 'c', 'd', 'e'}},
2024-12-09 00:56:53 -06:00
map[rune]struct{}{},
map[rune]struct{}{
'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {},
},
})
2024-12-09 00:56:53 -06:00
testLinkCase(t, linkTestCaseInput{
map[rune][]rune{
'a': {'b', 'c', 'd', 'e'},
'e': {'f', 'g', 'h', 'i'}},
2024-12-09 00:56:53 -06:00
map[rune]struct{}{},
map[rune]struct{}{
'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {}, 'i': {},
}})
// test single contract only without deps
testLinkCase(t, linkTestCaseInput{
map[rune][]rune{
'a': {}},
2024-12-09 00:56:53 -06:00
map[rune]struct{}{},
map[rune]struct{}{
'a': {},
}})
// test that libraries at different levels of the tree can share deps,
// and that these shared deps will only be deployed once.
testLinkCase(t, linkTestCaseInput{
map[rune][]rune{
'a': {'b', 'c', 'd', 'e'},
'e': {'f', 'g', 'h', 'i', 'm'},
'i': {'j', 'k', 'l', 'm'}},
2024-12-09 00:56:53 -06:00
map[rune]struct{}{},
map[rune]struct{}{
'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {}, 'i': {}, 'j': {}, 'k': {}, 'l': {}, 'm': {},
}})
// test two contracts can be deployed which don't share deps
testLinkCase(t, linkTestCaseInput{
map[rune][]rune{
'a': {'b', 'c', 'd', 'e'},
'f': {'g', 'h', 'i', 'j'}},
2024-12-09 00:56:53 -06:00
map[rune]struct{}{},
map[rune]struct{}{
'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {}, 'i': {}, 'j': {},
}})
// test two contracts can be deployed which share deps
testLinkCase(t, linkTestCaseInput{
map[rune][]rune{
'a': {'b', 'c', 'd', 'e'},
'f': {'g', 'c', 'd', 'h'}},
2024-12-09 00:56:53 -06:00
map[rune]struct{}{},
map[rune]struct{}{
'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {},
}})
2024-12-06 07:45:49 -06:00
2024-12-09 00:56:53 -06:00
// test one contract with overrides for all lib deps
testLinkCase(t, linkTestCaseInput{
map[rune][]rune{
'a': {'b', 'c', 'd', 'e'}},
2024-12-09 00:56:53 -06:00
map[rune]struct{}{'b': {}, 'c': {}, 'd': {}, 'e': {}},
map[rune]struct{}{
'a': {},
}})
2024-12-09 00:56:53 -06:00
// test one contract with overrides for some lib deps
testLinkCase(t, linkTestCaseInput{
map[rune][]rune{
'a': {'b', 'c'}},
2024-12-09 00:56:53 -06:00
map[rune]struct{}{'b': {}, 'c': {}},
map[rune]struct{}{
'a': {},
}})
2024-12-06 07:45:49 -06:00
// test deployment of a contract with overrides
testLinkCase(t, linkTestCaseInput{
map[rune][]rune{
'a': {}},
2024-12-09 00:56:53 -06:00
map[rune]struct{}{'a': {}},
map[rune]struct{}{}})
2024-12-09 00:56:53 -06:00
// 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{
2024-12-09 00:56:53 -06:00
'a': {'b', 'c', 'd', 'e'},
'f': {'g', 'c', 'd', 'h'}},
map[rune]struct{}{'a': {}},
map[rune]struct{}{
'f': {}, 'g': {}, 'c': {}, 'd': {}, 'h': {},
}})
2024-12-09 00:56:53 -06:00
2024-12-09 23:23:23 -06:00
// 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'},
'l': {'n', 'o', 'p'}},
2024-12-09 00:59:41 -06:00
map[rune]struct{}{
'i': {},
},
map[rune]struct{}{
'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {}, 'm': {},
}})
2024-12-09 00:56:53 -06:00
// TODO: same as the above case but nested one level of dependencies deep (?)
}