diff --git a/x/evm/embeds/artifacts/contracts/TestDirtyStateAttack3.sol/TestDirtyStateAttack3.json b/x/evm/embeds/artifacts/contracts/TestDirtyStateAttack3.sol/TestDirtyStateAttack3.json new file mode 100644 index 000000000..9b232ec8f --- /dev/null +++ b/x/evm/embeds/artifacts/contracts/TestDirtyStateAttack3.sol/TestDirtyStateAttack3.json @@ -0,0 +1,58 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "TestDirtyStateAttack3", + "sourceName": "contracts/TestDirtyStateAttack3.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "erc20_", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "sendRecipient", + "type": "address" + }, + { + "internalType": "string", + "name": "bech32Recipient", + "type": "string" + } + ], + "name": "attack", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "sendRecipient", + "type": "address" + }, + { + "internalType": "string", + "name": "bech32Recipient", + "type": "string" + } + ], + "name": "transferFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x6080604052600060015560405161094c38038061094c833981810160405281019061002a91906100d3565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610100565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100a082610075565b9050919050565b6100b081610095565b81146100bb57600080fd5b50565b6000815190506100cd816100a7565b92915050565b6000602082840312156100e9576100e8610070565b5b60006100f7848285016100be565b91505092915050565b61083d8061010f6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80631054aea21461003b578063e655beb414610057575b600080fd5b610055600480360381019061005091906104d3565b610073565b005b610071600480360381019061006c91906104d3565b610277565b005b8173ffffffffffffffffffffffffffffffffffffffff166108fc678ac7230489e800009081150290604051600060405180830381858888f193505050506100ef576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100e69061058c565b60405180910390fd5b600061080073ffffffffffffffffffffffffffffffffffffffff1660008054906101000a900473ffffffffffffffffffffffffffffffffffffffff16629896808460405160240161014293929190610654565b6040516020818303038152906040527fe77a47bf000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516101cc91906106d9565b6000604051808303816000865af19150503d8060008114610209576040519150601f19603f3d011682016040523d82523d6000602084013e61020e565b606091505b505090508060405160200161022290610716565b60405160208183030381529060405290610272576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610269919061072f565b60405180910390fd5b600080fd5b6001600081548092919061028a90610780565b91905055503073ffffffffffffffffffffffffffffffffffffffff16631054aea283836040518363ffffffff1660e01b81526004016102ca9291906107d7565b600060405180830381600087803b1580156102e457600080fd5b505af19250505080156102f5575060015b610316576001600081548092919061030c90610780565b9190505550610317565b5b5050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061035a8261032f565b9050919050565b61036a8161034f565b811461037557600080fd5b50565b60008135905061038781610361565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6103e082610397565b810181811067ffffffffffffffff821117156103ff576103fe6103a8565b5b80604052505050565b600061041261031b565b905061041e82826103d7565b919050565b600067ffffffffffffffff82111561043e5761043d6103a8565b5b61044782610397565b9050602081019050919050565b82818337600083830152505050565b600061047661047184610423565b610408565b90508281526020810184848401111561049257610491610392565b5b61049d848285610454565b509392505050565b600082601f8301126104ba576104b961038d565b5b81356104ca848260208601610463565b91505092915050565b600080604083850312156104ea576104e9610325565b5b60006104f885828601610378565b925050602083013567ffffffffffffffff8111156105195761051861032a565b5b610525858286016104a5565b9150509250929050565b600082825260208201905092915050565b7f455448207472616e73666572206661696c656400000000000000000000000000600082015250565b600061057660138361052f565b915061058182610540565b602082019050919050565b600060208201905081810360008301526105a581610569565b9050919050565b60006105b78261032f565b9050919050565b6105c7816105ac565b82525050565b6000819050919050565b6105e0816105cd565b82525050565b600081519050919050565b60005b8381101561060f5780820151818401526020810190506105f4565b60008484015250505050565b6000610626826105e6565b610630818561052f565b93506106408185602086016105f1565b61064981610397565b840191505092915050565b600060608201905061066960008301866105be565b61067660208301856105d7565b8181036040830152610688818461061b565b9050949350505050565b600081519050919050565b600081905092915050565b60006106b382610692565b6106bd818561069d565b93506106cd8185602086016105f1565b80840191505092915050565b60006106e582846106a8565b915081905092915050565b7f4661696c656420746f2063616c6c2062616e6b53656e64000000000000000000815250565b6000610721826106f0565b601782019150819050919050565b60006020820190508181036000830152610749818461061b565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061078b826105cd565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036107bd576107bc610751565b5b600182019050919050565b6107d18161034f565b82525050565b60006040820190506107ec60008301856107c8565b81810360208301526107fe818461061b565b9050939250505056fea2646970667358221220c3d57175350dbc81f4b427386910dce09422042c0b464bd34bfcdb6940a4715964736f6c63430008180033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80631054aea21461003b578063e655beb414610057575b600080fd5b610055600480360381019061005091906104d3565b610073565b005b610071600480360381019061006c91906104d3565b610277565b005b8173ffffffffffffffffffffffffffffffffffffffff166108fc678ac7230489e800009081150290604051600060405180830381858888f193505050506100ef576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100e69061058c565b60405180910390fd5b600061080073ffffffffffffffffffffffffffffffffffffffff1660008054906101000a900473ffffffffffffffffffffffffffffffffffffffff16629896808460405160240161014293929190610654565b6040516020818303038152906040527fe77a47bf000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516101cc91906106d9565b6000604051808303816000865af19150503d8060008114610209576040519150601f19603f3d011682016040523d82523d6000602084013e61020e565b606091505b505090508060405160200161022290610716565b60405160208183030381529060405290610272576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610269919061072f565b60405180910390fd5b600080fd5b6001600081548092919061028a90610780565b91905055503073ffffffffffffffffffffffffffffffffffffffff16631054aea283836040518363ffffffff1660e01b81526004016102ca9291906107d7565b600060405180830381600087803b1580156102e457600080fd5b505af19250505080156102f5575060015b610316576001600081548092919061030c90610780565b9190505550610317565b5b5050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061035a8261032f565b9050919050565b61036a8161034f565b811461037557600080fd5b50565b60008135905061038781610361565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6103e082610397565b810181811067ffffffffffffffff821117156103ff576103fe6103a8565b5b80604052505050565b600061041261031b565b905061041e82826103d7565b919050565b600067ffffffffffffffff82111561043e5761043d6103a8565b5b61044782610397565b9050602081019050919050565b82818337600083830152505050565b600061047661047184610423565b610408565b90508281526020810184848401111561049257610491610392565b5b61049d848285610454565b509392505050565b600082601f8301126104ba576104b961038d565b5b81356104ca848260208601610463565b91505092915050565b600080604083850312156104ea576104e9610325565b5b60006104f885828601610378565b925050602083013567ffffffffffffffff8111156105195761051861032a565b5b610525858286016104a5565b9150509250929050565b600082825260208201905092915050565b7f455448207472616e73666572206661696c656400000000000000000000000000600082015250565b600061057660138361052f565b915061058182610540565b602082019050919050565b600060208201905081810360008301526105a581610569565b9050919050565b60006105b78261032f565b9050919050565b6105c7816105ac565b82525050565b6000819050919050565b6105e0816105cd565b82525050565b600081519050919050565b60005b8381101561060f5780820151818401526020810190506105f4565b60008484015250505050565b6000610626826105e6565b610630818561052f565b93506106408185602086016105f1565b61064981610397565b840191505092915050565b600060608201905061066960008301866105be565b61067660208301856105d7565b8181036040830152610688818461061b565b9050949350505050565b600081519050919050565b600081905092915050565b60006106b382610692565b6106bd818561069d565b93506106cd8185602086016105f1565b80840191505092915050565b60006106e582846106a8565b915081905092915050565b7f4661696c656420746f2063616c6c2062616e6b53656e64000000000000000000815250565b6000610721826106f0565b601782019150819050919050565b60006020820190508181036000830152610749818461061b565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061078b826105cd565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036107bd576107bc610751565b5b600182019050919050565b6107d18161034f565b82525050565b60006040820190506107ec60008301856107c8565b81810360208301526107fe818461061b565b9050939250505056fea2646970667358221220c3d57175350dbc81f4b427386910dce09422042c0b464bd34bfcdb6940a4715964736f6c63430008180033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/x/evm/embeds/contracts/TestDirtyStateAttack3.sol b/x/evm/embeds/contracts/TestDirtyStateAttack3.sol new file mode 100644 index 000000000..16730b2de --- /dev/null +++ b/x/evm/embeds/contracts/TestDirtyStateAttack3.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +// Uncomment this line to use console.log +// import "hardhat/console.sol"; +import "./IFunToken.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract TestDirtyStateAttack3 { + address erc20; + uint counter = 0; + + constructor(address erc20_) payable { + erc20 = erc20_; + } + + function attack( + address payable sendRecipient, + string memory bech32Recipient + ) external { + counter++; + try + TestDirtyStateAttack3(payable(address(this))).transferFunds( + sendRecipient, + bech32Recipient + ) + {} catch + { + counter++; + } + } + + function transferFunds( + address payable sendRecipient, + string memory bech32Recipient + ) external { + require(sendRecipient.send(10 ether), "ETH transfer failed"); // 10 NIBI + + (bool success, ) = FUNTOKEN_PRECOMPILE_ADDRESS.call( + abi.encodeWithSignature( + "sendToBank(address,uint256,string)", + erc20, + uint256(10e6), // 10 WNIBI + bech32Recipient + ) + ); + + require(success, string.concat("Failed to call bankSend")); + + revert(); + } +} diff --git a/x/evm/embeds/embeds.go b/x/evm/embeds/embeds.go index 83e6b55be..60f496843 100644 --- a/x/evm/embeds/embeds.go +++ b/x/evm/embeds/embeds.go @@ -51,6 +51,8 @@ var ( testDirtyStateAttack1 []byte //go:embed artifacts/contracts/TestDirtyStateAttack2.sol/TestDirtyStateAttack2.json testDirtyStateAttack2 []byte + //go:embed artifacts/contracts/TestDirtyStateAttack3.sol/TestDirtyStateAttack3.json + testDirtyStateAttack3 []byte ) var ( @@ -158,16 +160,21 @@ var ( Name: "TestPrecompileSendToBankThenERC20Transfer.sol", EmbedJSON: testPrecompileSendToBankThenERC20Transfer, } - // SmartContract_TestDirtyStateAttack1 is a test contract that sends to bank then calls ERC20 transfer + // SmartContract_TestDirtyStateAttack1 is a test contract that composes manual send and funtoken sendToBank SmartContract_TestDirtyStateAttack1 = CompiledEvmContract{ Name: "TestDirtyStateAttack1.sol", EmbedJSON: testDirtyStateAttack1, } - // SmartContract_TestDirtyStateAttack2 is a test contract that sends to bank then calls ERC20 transfer + // SmartContract_TestDirtyStateAttack2 is a test contract that composes erc20 transfer and funtoken sendToBank SmartContract_TestDirtyStateAttack2 = CompiledEvmContract{ Name: "TestDirtyStateAttack2.sol", EmbedJSON: testDirtyStateAttack2, } + // SmartContract_TestDirtyStateAttack3 is a test contract that composes manual send and funtoken sendToBank with a reversion + SmartContract_TestDirtyStateAttack3 = CompiledEvmContract{ + Name: "TestDirtyStateAttack3.sol", + EmbedJSON: testDirtyStateAttack3, + } ) func init() { @@ -189,6 +196,7 @@ func init() { SmartContract_TestPrecompileSendToBankThenERC20Transfer.MustLoad() SmartContract_TestDirtyStateAttack1.MustLoad() SmartContract_TestDirtyStateAttack2.MustLoad() + SmartContract_TestDirtyStateAttack3.MustLoad() } type CompiledEvmContract struct { diff --git a/x/evm/keeper/funtoken_from_coin_test.go b/x/evm/keeper/funtoken_from_coin_test.go index 1c93354f6..e0e97203f 100644 --- a/x/evm/keeper/funtoken_from_coin_test.go +++ b/x/evm/keeper/funtoken_from_coin_test.go @@ -958,6 +958,106 @@ func (s *FunTokenFromCoinSuite) TestDirtyStateAttack2() { }) } +// TestDirtyStateAttack3 +// 1. Creates a funtoken from coin. +// 2. Calls the test contract +// a. manual send 10 NIBI to Alice +// b. FunToken.sendToBank 10 WNIBI to Alice +// c. reverts +// +// INITIAL STATE: +// - Test contract funds: 10 WNIBI, 10 NIBI +// CONTRACT CALL: +// - Sends 10 NIBI to Alice manually +// - Sends 10 WNIBI to Alice via FunToken.sendToBank +// - reverts +// EXPECTED: +// - Test contract funds: 10 WNIBI, 10 NIBI +// - Alice: 0 WNIBI, 0 NIBI +// - Module account: 10 NIBI escrowed +func (s *FunTokenFromCoinSuite) TestDirtyStateAttack3() { + deps := evmtest.NewTestDeps() + + // Initial setup + funToken := s.fundAndCreateFunToken(deps, 10e6) + + s.T().Log("Deploy Test Contract") + deployResp, err := evmtest.DeployContract( + &deps, + embeds.SmartContract_TestDirtyStateAttack3, + funToken.Erc20Addr.Address, + ) + s.Require().NoError(err) + testContractAddr := deployResp.ContractAddr + + s.Run("Convert bank coin to erc-20: give test contract 10 WNIBI (erc20)", func() { + _, err = deps.EvmKeeper.ConvertCoinToEvm( + sdk.WrapSDKContext(deps.Ctx), + &evm.MsgConvertCoinToEvm{ + Sender: deps.Sender.NibiruAddr.String(), + BankCoin: sdk.NewCoin(evm.EVMBankDenom, sdk.NewInt(10e6)), + ToEthAddr: eth.EIP55Addr{Address: testContractAddr}, + }, + ) + s.Require().NoError(err) + }) + + s.Run("Send 10 NIBI to test contract manually", func() { + s.Require().NoError(testapp.FundAccount( + deps.App.BankKeeper, + deps.Ctx, + eth.EthAddrToNibiruAddr(testContractAddr), + sdk.NewCoins(sdk.NewCoin(evm.EVMBankDenom, sdk.NewInt(10e6))), + )) + }) + + alice := evmtest.NewEthPrivAcc() + + s.Run("call test contract", func() { + contractInput, err := embeds.SmartContract_TestDirtyStateAttack3.ABI.Pack( + "attack", + alice.EthAddr, + alice.NibiruAddr.String(), + ) + s.Require().NoError(err) + evmObj, _ := deps.NewEVM() + _, err = deps.EvmKeeper.CallContractWithInput( + deps.Ctx, + evmObj, + deps.Sender.EthAddr, + &testContractAddr, + true, + contractInput, + evmtest.FunTokenGasLimitSendToEvm, + ) + s.Require().NoError(err) + + evmtest.FunTokenBalanceAssert{ + FunToken: funToken, + Account: alice.EthAddr, + BalanceBank: big.NewInt(0), + BalanceERC20: big.NewInt(0), + Description: "Alice has 0 NIBI / 0 WNIBI", + }.Assert(s.T(), deps, evmObj) + + evmtest.FunTokenBalanceAssert{ + FunToken: funToken, + Account: testContractAddr, + BalanceBank: big.NewInt(10e6), + BalanceERC20: big.NewInt(10e6), + Description: "Test contract has 10 WNIBI / 10 NIBI", + }.Assert(s.T(), deps, evmObj) + + evmtest.FunTokenBalanceAssert{ + FunToken: funToken, + Account: evm.EVM_MODULE_ADDRESS, + BalanceBank: big.NewInt(10e6), + BalanceERC20: big.NewInt(0), + Description: "Module account has 10 NIBI escrowed", + }.Assert(s.T(), deps, evmObj) + }) +} + // fundAndCreateFunToken creates initial setup for tests func (s *FunTokenFromCoinSuite) fundAndCreateFunToken(deps evmtest.TestDeps, unibiAmount int64) evm.FunToken { bankDenom := evm.EVMBankDenom