diff --git a/ethartifact/ethartifact.go b/ethartifact/ethartifact.go index cef31707..f2df236a 100644 --- a/ethartifact/ethartifact.go +++ b/ethartifact/ethartifact.go @@ -70,6 +70,54 @@ type RawArtifact struct { DeployedBytecode string `json:"deployedBytecode"` } +type FoundryRawArtifact struct { + ABI json.RawMessage `json:"abi"` + Bytecode struct { + Object string `json:"object"` + } `json:"bytecode"` + DeployedBytecode struct { + Object string `json:"object"` + } `json:"deployedBytecode"` + Metadata struct { + Settings struct { + CompilationTarget map[string]string `json:"compilationTarget"` + } `json:"settings"` + } `json:"metadata"` +} + +func ParseFoundryArtifactFile(path string) (FoundryRawArtifact, error) { + filedata, err := os.ReadFile(path) + if err != nil { + return FoundryRawArtifact{}, err + } + + var artifact FoundryRawArtifact + err = json.Unmarshal(filedata, &artifact) + if err != nil { + return FoundryRawArtifact{}, err + } + + return artifact, nil +} + +func (f FoundryRawArtifact) ToRawArtifact() (RawArtifact, error) { + // Contract name is the only value in the compilation target map + if len(f.Metadata.Settings.CompilationTarget) != 1 { + return RawArtifact{}, fmt.Errorf("expected exactly one compilation target, got %d", len(f.Metadata.Settings.CompilationTarget)) + } + var contractName string + for _, v := range f.Metadata.Settings.CompilationTarget { + contractName = v + } + + return RawArtifact{ + ContractName: contractName, + ABI: f.ABI, + Bytecode: f.Bytecode.Object, + DeployedBytecode: f.DeployedBytecode.Object, + }, nil +} + func ParseArtifactFile(path string) (RawArtifact, error) { filedata, err := os.ReadFile(path) if err != nil { @@ -79,7 +127,13 @@ func ParseArtifactFile(path string) (RawArtifact, error) { var artifact RawArtifact err = json.Unmarshal(filedata, &artifact) if err != nil { - return RawArtifact{}, err + // Try parsing as foundry artifact + foundryArtifact, foundryErr := ParseFoundryArtifactFile(path) + if foundryErr != nil { + // Return the original error + return RawArtifact{}, err + } + return foundryArtifact.ToRawArtifact() } return artifact, nil diff --git a/ethartifact/ethartifact_test.go b/ethartifact/ethartifact_test.go new file mode 100644 index 00000000..1bfb797b --- /dev/null +++ b/ethartifact/ethartifact_test.go @@ -0,0 +1,83 @@ +package ethartifact + +import ( + "os" + "strings" + "testing" +) + +func TestParseArtifactFile_Hardhat(t *testing.T) { + hardhatPath := "testdata/hardhat_artifact.json" + if _, err := os.Stat(hardhatPath); err != nil { + t.Skipf("Hardhat artifact file not found: %v", err) + } + + artifact, err := ParseArtifactFile(hardhatPath) + if err != nil { + t.Fatalf("ParseArtifactFile failed for Hardhat artifact: %v", err) + } + + // Check contract name + if artifact.ContractName != "ERC20" { + t.Errorf("Expected contract name 'ERC20', got '%s'", artifact.ContractName) + } + + // Check ABI is not empty + if len(artifact.ABI) == 0 { + t.Error("Expected ABI to be parsed, but it's empty") + } + + // Check bytecode is not empty and has 0x prefix + if len(artifact.Bytecode) == 0 { + t.Error("Expected bytecode to be parsed, but it's empty") + } + if !strings.HasPrefix(artifact.Bytecode, "0x") { + t.Error("Expected bytecode to start with '0x'") + } + + // Check deployed bytecode is not empty and has 0x prefix + if len(artifact.DeployedBytecode) == 0 { + t.Error("Expected deployed bytecode to be parsed, but it's empty") + } + if !strings.HasPrefix(artifact.DeployedBytecode, "0x") { + t.Error("Expected deployed bytecode to start with '0x'") + } +} + +func TestParseArtifactFile_Foundry(t *testing.T) { + foundryPath := "testdata/foundry_artifact.json" + if _, err := os.Stat(foundryPath); err != nil { + t.Skipf("Foundry artifact file not found: %v", err) + } + + artifact, err := ParseArtifactFile(foundryPath) + if err != nil { + t.Fatalf("ParseArtifactFile failed for Foundry artifact: %v", err) + } + + // Check contract name + if artifact.ContractName != "ValueForwarder" { + t.Errorf("Expected contract name 'ValueForwarder', got '%s'", artifact.ContractName) + } + + // Check ABI is not empty + if len(artifact.ABI) == 0 { + t.Error("Expected ABI to be parsed, but it's empty") + } + + // Check bytecode is not empty and has 0x prefix + if len(artifact.Bytecode) == 0 { + t.Error("Expected bytecode to be parsed, but it's empty") + } + if !strings.HasPrefix(artifact.Bytecode, "0x") { + t.Error("Expected bytecode to start with '0x'") + } + + // Check deployed bytecode is not empty and has 0x prefix + if len(artifact.DeployedBytecode) == 0 { + t.Error("Expected deployed bytecode to be parsed, but it's empty") + } + if !strings.HasPrefix(artifact.DeployedBytecode, "0x") { + t.Error("Expected deployed bytecode to start with '0x'") + } +} diff --git a/ethartifact/testdata/foundry_artifact.json b/ethartifact/testdata/foundry_artifact.json new file mode 100644 index 00000000..a5822935 --- /dev/null +++ b/ethartifact/testdata/foundry_artifact.json @@ -0,0 +1,76 @@ +{ + "abi": [ + { + "type": "function", + "name": "forwardValue", + "inputs": [ + { "name": "to", "type": "address", "internalType": "address" }, + { "name": "value", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [], + "stateMutability": "payable" + } + ], + "bytecode": { + "object": "0x6080806040523460155761014f908161001b8239f35b600080fdfe6080604052600436101561001257600080fd5b60003560e01c6398f850f11461002757600080fd5b6040366003190112610114576004356001600160a01b038116810361011457600080808093602435905af13d1561010f573d67ffffffffffffffff81116100f95760405190601f8101601f19908116603f0116820167ffffffffffffffff8111838210176100f9576040528152600060203d92013e5b156100a457005b60405162461bcd60e51b815260206004820152602760248201527f56616c7565466f727761726465723a204661696c656420746f20666f72776172604482015266642076616c756560c81b6064820152608490fd5b634e487b7160e01b600052604160045260246000fd5b61009d565b600080fdfea26469706673582212202706a11f313e042ccf1cd42b40b5f01308364a2290592fe4e14dce33175da1bf64736f6c634300081c0033", + "sourceMap": "151:216:42:-:0;;;;;;;;;;;;;;;;;", + "linkReferences": {} + }, + "deployedBytecode": { + "object": "0x6080604052600436101561001257600080fd5b60003560e01c6398f850f11461002757600080fd5b6040366003190112610114576004356001600160a01b038116810361011457600080808093602435905af13d1561010f573d67ffffffffffffffff81116100f95760405190601f8101601f19908116603f0116820167ffffffffffffffff8111838210176100f9576040528152600060203d92013e5b156100a457005b60405162461bcd60e51b815260206004820152602760248201527f56616c7565466f727761726465723a204661696c656420746f20666f72776172604482015266642076616c756560c81b6064820152608490fd5b634e487b7160e01b600052604160045260246000fd5b61009d565b600080fdfea26469706673582212202706a11f313e042ccf1cd42b40b5f01308364a2290592fe4e14dce33175da1bf64736f6c634300081c0033", + "sourceMap": "151:216:42:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;151:216:42;;;;;;-1:-1:-1;;;;;151:216:42;;;;;;;;;;;;;270:25;;;151:216;;;;;;;;;;;;;;;;-1:-1:-1;;151:216:42;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;151:216:42;;;;;;;;;;;;;;;;;-1:-1:-1;;;151:216:42;;;;;;;;;;;;;;;;;;;;;;;;;", + "linkReferences": {} + }, + "methodIdentifiers": { "forwardValue(address,uint256)": "98f850f1" }, + "rawMetadata": "{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"forwardValue\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Michael Standen\",\"kind\":\"dev\",\"methods\":{},\"title\":\"ValueForwarder\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"Forwarder for value\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"test/mocks/ValueForwarder.sol\":\"ValueForwarder\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/\",\":erc2470-libs/=lib/erc2470-libs/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\"],\"viaIR\":true},\"sources\":{\"test/mocks/ValueForwarder.sol\":{\"keccak256\":\"0x2c86b5ca1001b169892de7cd3b069c5d29223e54a8701350cae39502cef5d8a3\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://c880a96377be176d3385a44653ed62e1678b88da9141582d8fbb5f0a6165bfe2\",\"dweb:/ipfs/QmXtDzW2wewKnPp56ntZtCM7AatTpi5M3YWZPvMAmWM15E\"]}},\"version\":1}", + "metadata": { + "compiler": { "version": "0.8.28+commit.7893614a" }, + "language": "Solidity", + "output": { + "abi": [ + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "stateMutability": "payable", + "type": "function", + "name": "forwardValue" + } + ], + "devdoc": { "kind": "dev", "methods": {}, "version": 1 }, + "userdoc": { "kind": "user", "methods": {}, "version": 1 } + }, + "settings": { + "remappings": [ + "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", + "erc2470-libs/=lib/erc2470-libs/", + "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/", + "forge-std/=lib/forge-std/src/", + "halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/", + "openzeppelin-contracts/=lib/openzeppelin-contracts/" + ], + "optimizer": { "enabled": true, "runs": 200 }, + "metadata": { "bytecodeHash": "ipfs" }, + "compilationTarget": { + "test/mocks/ValueForwarder.sol": "ValueForwarder" + }, + "evmVersion": "paris", + "libraries": {}, + "viaIR": true + }, + "sources": { + "test/mocks/ValueForwarder.sol": { + "keccak256": "0x2c86b5ca1001b169892de7cd3b069c5d29223e54a8701350cae39502cef5d8a3", + "urls": [ + "bzz-raw://c880a96377be176d3385a44653ed62e1678b88da9141582d8fbb5f0a6165bfe2", + "dweb:/ipfs/QmXtDzW2wewKnPp56ntZtCM7AatTpi5M3YWZPvMAmWM15E" + ], + "license": "Apache-2.0" + } + }, + "version": 1 + }, + "id": 42 + } + \ No newline at end of file diff --git a/ethartifact/testdata/hardhat_artifact.json b/ethartifact/testdata/hardhat_artifact.json new file mode 100644 index 00000000..0b4bb795 --- /dev/null +++ b/ethartifact/testdata/hardhat_artifact.json @@ -0,0 +1,243 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "ERC20", + "sourceName": "contracts/mocks/ERC20Mock.sol", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b50610687806100206000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c806370a082311161005b57806370a0823114610170578063a457c2d7146101a3578063a9059cbb146101dc578063dd62ed3e1461021557610088565b8063095ea7b31461008d57806318160ddd146100da57806323b872dd146100f45780633950935114610137575b600080fd5b6100c6600480360360408110156100a357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610250565b604080519115158252519081900360200190f35b6100e2610266565b60408051918252519081900360200190f35b6100c66004803603606081101561010a57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020810135909116906040013561026c565b6100c66004803603604081101561014d57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356102ca565b6100e26004803603602081101561018657600080fd5b503573ffffffffffffffffffffffffffffffffffffffff1661030d565b6100c6600480360360408110156101b957600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610335565b6100c6600480360360408110156101f257600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610378565b6100e26004803603604081101561022b57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516610385565b600061025d3384846103bd565b50600192915050565b60025490565b600061027984848461046c565b73ffffffffffffffffffffffffffffffffffffffff84166000908152600160209081526040808320338085529252909120546102c09186916102bb908661055f565b6103bd565b5060019392505050565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff87168452909152812054909161025d9185906102bb90866105d6565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff87168452909152812054909161025d9185906102bb908661055f565b600061025d33848461046c565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260016020908152604080832093909416825291909152205490565b73ffffffffffffffffffffffffffffffffffffffff82166103dd57600080fd5b73ffffffffffffffffffffffffffffffffffffffff83166103fd57600080fd5b73ffffffffffffffffffffffffffffffffffffffff808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b73ffffffffffffffffffffffffffffffffffffffff821661048c57600080fd5b73ffffffffffffffffffffffffffffffffffffffff83166000908152602081905260409020546104bc908261055f565b73ffffffffffffffffffffffffffffffffffffffff80851660009081526020819052604080822093909355908416815220546104f890826105d6565b73ffffffffffffffffffffffffffffffffffffffff8084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b6000828211156105d057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f536166654d617468237375623a20554e444552464c4f57000000000000000000604482015290519081900360640190fd5b50900390565b60008282018381101561064a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f536166654d617468236164643a204f564552464c4f5700000000000000000000604482015290519081900360640190fd5b939250505056fea26469706673582212203c4dcfdef3f739fe1242da086388e24ba7e6944f058353c4fc15e984b4a21e2c64736f6c63430007040033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100885760003560e01c806370a082311161005b57806370a0823114610170578063a457c2d7146101a3578063a9059cbb146101dc578063dd62ed3e1461021557610088565b8063095ea7b31461008d57806318160ddd146100da57806323b872dd146100f45780633950935114610137575b600080fd5b6100c6600480360360408110156100a357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610250565b604080519115158252519081900360200190f35b6100e2610266565b60408051918252519081900360200190f35b6100c66004803603606081101561010a57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020810135909116906040013561026c565b6100c66004803603604081101561014d57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356102ca565b6100e26004803603602081101561018657600080fd5b503573ffffffffffffffffffffffffffffffffffffffff1661030d565b6100c6600480360360408110156101b957600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610335565b6100c6600480360360408110156101f257600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610378565b6100e26004803603604081101561022b57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516610385565b600061025d3384846103bd565b50600192915050565b60025490565b600061027984848461046c565b73ffffffffffffffffffffffffffffffffffffffff84166000908152600160209081526040808320338085529252909120546102c09186916102bb908661055f565b6103bd565b5060019392505050565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff87168452909152812054909161025d9185906102bb90866105d6565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff87168452909152812054909161025d9185906102bb908661055f565b600061025d33848461046c565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260016020908152604080832093909416825291909152205490565b73ffffffffffffffffffffffffffffffffffffffff82166103dd57600080fd5b73ffffffffffffffffffffffffffffffffffffffff83166103fd57600080fd5b73ffffffffffffffffffffffffffffffffffffffff808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b73ffffffffffffffffffffffffffffffffffffffff821661048c57600080fd5b73ffffffffffffffffffffffffffffffffffffffff83166000908152602081905260409020546104bc908261055f565b73ffffffffffffffffffffffffffffffffffffffff80851660009081526020819052604080822093909355908416815220546104f890826105d6565b73ffffffffffffffffffffffffffffffffffffffff8084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b6000828211156105d057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f536166654d617468237375623a20554e444552464c4f57000000000000000000604482015290519081900360640190fd5b50900390565b60008282018381101561064a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f536166654d617468236164643a204f564552464c4f5700000000000000000000604482015290519081900360640190fd5b939250505056fea26469706673582212203c4dcfdef3f739fe1242da086388e24ba7e6944f058353c4fc15e984b4a21e2c64736f6c63430007040033", + "linkReferences": {}, + "deployedLinkReferences": {} + } + \ No newline at end of file