From 9d231c3f313c8b0d9ca11d35055a3c2d05c234fa Mon Sep 17 00:00:00 2001 From: Kevin Yang <5478483+k-yang@users.noreply.github.com> Date: Wed, 22 Jan 2025 12:38:51 -0800 Subject: [PATCH] test: add TestDirtyStateAttack1 --- .../TestDirtyStateAttack1.json | 40 +++++++ .../contracts/TestDirtyStateAttack1.sol | 34 ++++++ x/evm/embeds/embeds.go | 10 +- x/evm/keeper/funtoken_from_coin_test.go | 101 ++++++++++++++++++ 4 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 x/evm/embeds/artifacts/contracts/TestDirtyStateAttack1.sol/TestDirtyStateAttack1.json create mode 100644 x/evm/embeds/contracts/TestDirtyStateAttack1.sol diff --git a/x/evm/embeds/artifacts/contracts/TestDirtyStateAttack1.sol/TestDirtyStateAttack1.json b/x/evm/embeds/artifacts/contracts/TestDirtyStateAttack1.sol/TestDirtyStateAttack1.json new file mode 100644 index 000000000..190d3dc8a --- /dev/null +++ b/x/evm/embeds/artifacts/contracts/TestDirtyStateAttack1.sol/TestDirtyStateAttack1.json @@ -0,0 +1,40 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "TestDirtyStateAttack1", + "sourceName": "contracts/TestDirtyStateAttack1.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "erc20_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "sendRecipient", + "type": "address" + }, + { + "internalType": "string", + "name": "bech32Recipient", + "type": "string" + } + ], + "name": "attack", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b506040516107da3803806107da833981810160405281019061003291906100db565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610108565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100a88261007d565b9050919050565b6100b88161009d565b81146100c357600080fd5b50565b6000815190506100d5816100af565b92915050565b6000602082840312156100f1576100f0610078565b5b60006100ff848285016100c6565b91505092915050565b6106c3806101176000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063e655beb414610030575b600080fd5b61004a6004803603810190610045919061040f565b61004c565b005b60008273ffffffffffffffffffffffffffffffffffffffff166108fc678ac7230489e800009081150290604051600060405180830381858888f193505050509050806100cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100c4906104c8565b60405180910390fd5b600061080073ffffffffffffffffffffffffffffffffffffffff1660008054906101000a900473ffffffffffffffffffffffffffffffffffffffff16629896808560405160240161012093929190610590565b6040516020818303038152906040527fe77a47bf000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516101aa9190610615565b6000604051808303816000865af19150503d80600081146101e7576040519150601f19603f3d011682016040523d82523d6000602084013e6101ec565b606091505b505090508060405160200161020090610652565b60405160208183030381529060405290610250576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610247919061066b565b60405180910390fd5b5050505050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102968261026b565b9050919050565b6102a68161028b565b81146102b157600080fd5b50565b6000813590506102c38161029d565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61031c826102d3565b810181811067ffffffffffffffff8211171561033b5761033a6102e4565b5b80604052505050565b600061034e610257565b905061035a8282610313565b919050565b600067ffffffffffffffff82111561037a576103796102e4565b5b610383826102d3565b9050602081019050919050565b82818337600083830152505050565b60006103b26103ad8461035f565b610344565b9050828152602081018484840111156103ce576103cd6102ce565b5b6103d9848285610390565b509392505050565b600082601f8301126103f6576103f56102c9565b5b813561040684826020860161039f565b91505092915050565b6000806040838503121561042657610425610261565b5b6000610434858286016102b4565b925050602083013567ffffffffffffffff81111561045557610454610266565b5b610461858286016103e1565b9150509250929050565b600082825260208201905092915050565b7f4661696c656420746f2073656e64206574686572000000000000000000000000600082015250565b60006104b260148361046b565b91506104bd8261047c565b602082019050919050565b600060208201905081810360008301526104e1816104a5565b9050919050565b60006104f38261026b565b9050919050565b610503816104e8565b82525050565b6000819050919050565b61051c81610509565b82525050565b600081519050919050565b60005b8381101561054b578082015181840152602081019050610530565b60008484015250505050565b600061056282610522565b61056c818561046b565b935061057c81856020860161052d565b610585816102d3565b840191505092915050565b60006060820190506105a560008301866104fa565b6105b26020830185610513565b81810360408301526105c48184610557565b9050949350505050565b600081519050919050565b600081905092915050565b60006105ef826105ce565b6105f981856105d9565b935061060981856020860161052d565b80840191505092915050565b600061062182846105e4565b915081905092915050565b7f4661696c656420746f2063616c6c2062616e6b53656e64000000000000000000815250565b600061065d8261062c565b601782019150819050919050565b600060208201905081810360008301526106858184610557565b90509291505056fea2646970667358221220b1d6754ff01a90b91186ccf26633cba5add088b2b0240f20b7d32cd876e2820764736f6c63430008180033", + "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063e655beb414610030575b600080fd5b61004a6004803603810190610045919061040f565b61004c565b005b60008273ffffffffffffffffffffffffffffffffffffffff166108fc678ac7230489e800009081150290604051600060405180830381858888f193505050509050806100cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100c4906104c8565b60405180910390fd5b600061080073ffffffffffffffffffffffffffffffffffffffff1660008054906101000a900473ffffffffffffffffffffffffffffffffffffffff16629896808560405160240161012093929190610590565b6040516020818303038152906040527fe77a47bf000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516101aa9190610615565b6000604051808303816000865af19150503d80600081146101e7576040519150601f19603f3d011682016040523d82523d6000602084013e6101ec565b606091505b505090508060405160200161020090610652565b60405160208183030381529060405290610250576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610247919061066b565b60405180910390fd5b5050505050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102968261026b565b9050919050565b6102a68161028b565b81146102b157600080fd5b50565b6000813590506102c38161029d565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61031c826102d3565b810181811067ffffffffffffffff8211171561033b5761033a6102e4565b5b80604052505050565b600061034e610257565b905061035a8282610313565b919050565b600067ffffffffffffffff82111561037a576103796102e4565b5b610383826102d3565b9050602081019050919050565b82818337600083830152505050565b60006103b26103ad8461035f565b610344565b9050828152602081018484840111156103ce576103cd6102ce565b5b6103d9848285610390565b509392505050565b600082601f8301126103f6576103f56102c9565b5b813561040684826020860161039f565b91505092915050565b6000806040838503121561042657610425610261565b5b6000610434858286016102b4565b925050602083013567ffffffffffffffff81111561045557610454610266565b5b610461858286016103e1565b9150509250929050565b600082825260208201905092915050565b7f4661696c656420746f2073656e64206574686572000000000000000000000000600082015250565b60006104b260148361046b565b91506104bd8261047c565b602082019050919050565b600060208201905081810360008301526104e1816104a5565b9050919050565b60006104f38261026b565b9050919050565b610503816104e8565b82525050565b6000819050919050565b61051c81610509565b82525050565b600081519050919050565b60005b8381101561054b578082015181840152602081019050610530565b60008484015250505050565b600061056282610522565b61056c818561046b565b935061057c81856020860161052d565b610585816102d3565b840191505092915050565b60006060820190506105a560008301866104fa565b6105b26020830185610513565b81810360408301526105c48184610557565b9050949350505050565b600081519050919050565b600081905092915050565b60006105ef826105ce565b6105f981856105d9565b935061060981856020860161052d565b80840191505092915050565b600061062182846105e4565b915081905092915050565b7f4661696c656420746f2063616c6c2062616e6b53656e64000000000000000000815250565b600061065d8261062c565b601782019150819050919050565b600060208201905081810360008301526106858184610557565b90509291505056fea2646970667358221220b1d6754ff01a90b91186ccf26633cba5add088b2b0240f20b7d32cd876e2820764736f6c63430008180033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/x/evm/embeds/contracts/TestDirtyStateAttack1.sol b/x/evm/embeds/contracts/TestDirtyStateAttack1.sol new file mode 100644 index 000000000..93b7bb763 --- /dev/null +++ b/x/evm/embeds/contracts/TestDirtyStateAttack1.sol @@ -0,0 +1,34 @@ +// 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"; + +contract TestDirtyStateAttack1 { + address erc20; + + constructor(address erc20_) { + erc20 = erc20_; + } + + function attack( + address payable sendRecipient, + string memory bech32Recipient + ) public { + bool isSent = sendRecipient.send(10 ether); // 10 NIBI + require(isSent, "Failed to send ether"); + + (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")); + } +} diff --git a/x/evm/embeds/embeds.go b/x/evm/embeds/embeds.go index f08e26b5d..0837dc995 100644 --- a/x/evm/embeds/embeds.go +++ b/x/evm/embeds/embeds.go @@ -47,6 +47,8 @@ var ( testMetadataBytes32 []byte //go:embed artifacts/contracts/TestPrecompileSendToBankThenERC20Transfer.sol/TestPrecompileSendToBankThenERC20Transfer.json testPrecompileSendToBankThenERC20Transfer []byte + //go:embed artifacts/contracts/TestDirtyStateAttack1.sol/TestDirtyStateAttack1.json + testDirtyStateAttack1 []byte ) var ( @@ -118,7 +120,6 @@ var ( Name: "TestERC20TransferThenPrecompileSend.sol", EmbedJSON: testERC20TransferThenPrecompileSendJson, } - // SmartContract_TestPrecompileSelfCallRevert is a test contract // that creates another instance of itself, calls the precompile method and then force reverts. // It tests a race condition where the state DB commit @@ -150,12 +151,16 @@ var ( Name: "MKR.sol", EmbedJSON: testMetadataBytes32, } - // SmartContract_TestPrecompileSendToBankThenERC20Transfer is a test contract that sends to bank then calls ERC20 transfer SmartContract_TestPrecompileSendToBankThenERC20Transfer = CompiledEvmContract{ Name: "TestPrecompileSendToBankThenERC20Transfer.sol", EmbedJSON: testPrecompileSendToBankThenERC20Transfer, } + // SmartContract_TestDirtyStateAttack1 is a test contract that sends to bank then calls ERC20 transfer + SmartContract_TestDirtyStateAttack1 = CompiledEvmContract{ + Name: "TestDirtyStateAttack1.sol", + EmbedJSON: testDirtyStateAttack1, + } ) func init() { @@ -175,6 +180,7 @@ func init() { SmartContract_TestRandom.MustLoad() SmartContract_TestBytes32Metadata.MustLoad() SmartContract_TestPrecompileSendToBankThenERC20Transfer.MustLoad() + SmartContract_TestDirtyStateAttack1.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 7a75afca6..1d1eb32e2 100644 --- a/x/evm/keeper/funtoken_from_coin_test.go +++ b/x/evm/keeper/funtoken_from_coin_test.go @@ -771,6 +771,107 @@ func (s *FunTokenFromCoinSuite) TestPrecompileSendToBankThenErc20Transfer() { }.Assert(s.T(), deps, evmObj) } +// TestDirtyStateAttack1 +// 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 +// +// 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 +// EXPECTED: +// - Test contract funds: 0 WNIBI, 0 NIBI +// - Alice: 20 NIBI +// - Module account: 0 NIBI escrowed +func (s *FunTokenFromCoinSuite) TestDirtyStateAttack1() { + deps := evmtest.NewTestDeps() + + // Initial setup + funToken := s.fundAndCreateFunToken(deps, 10e6) + + s.T().Log("Deploy Test Contract") + deployResp, err := evmtest.DeployContract( + &deps, + embeds.SmartContract_TestDirtyStateAttack1, + 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))), + )) + }) + + // Create Alice and Bob. Contract will try to send Alice native coins and + // send Bob ERC20 tokens. + alice := evmtest.NewEthPrivAcc() + + s.T().Log("call test contract") + s.Run("call test contract", func() { + contractInput, err := embeds.SmartContract_TestDirtyStateAttack1.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(20e6), + BalanceERC20: big.NewInt(0), + Description: "Alice has 20 NIBI / 0 WNIBI", + }.Assert(s.T(), deps, evmObj) + + evmtest.FunTokenBalanceAssert{ + FunToken: funToken, + Account: testContractAddr, + BalanceBank: big.NewInt(0), + BalanceERC20: big.NewInt(0), + Description: "Test contract has 0 WNIBI / 0 NIBI", + }.Assert(s.T(), deps, evmObj) + + evmtest.FunTokenBalanceAssert{ + FunToken: funToken, + Account: evm.EVM_MODULE_ADDRESS, + BalanceBank: big.NewInt(0), + BalanceERC20: big.NewInt(0), + Description: "Module account has 0 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