Skip to content

Commit

Permalink
Merge branch 'version-1.5.x' into NODE-2609
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-mashonskiy authored Sep 25, 2023
2 parents a3faefa + 65646ef commit e8e5c5d
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ object AssetTransactionsDiffs {
cond(
!b.isFeatureActivated(BlockRewardDistribution) || script.bytes().size <= MaxExprSizeInBytes,
(),
GenericError("Invalid script")
GenericError(s"Script is too large: ${script.bytes().size} bytes > $MaxExprSizeInBytes bytes")
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,14 @@ final case class EthABIConverter(script: Script) {

lazy val ethMethodId: String = EthABIConverter.buildMethodId(ethSignature)

def checkLen(func: Function, tuple: Tuple, len: Int, check: Boolean) = {
def checkLen(func: Function, tuple: Tuple, len: Int, check: Boolean): Either[GenericError, Unit] = {
val cls = Class.forName("com.esaulpaugh.headlong.abi.TupleType")
val method = cls.getDeclaredMethod("byteLength", classOf[Tuple])
method.setAccessible(true)
Either.cond(
!check || method.invoke(func.getInputs, tuple).asInstanceOf[Int] == len - Function.SELECTOR_LEN,
(),
GenericError("unconsumed bytes remaining")
GenericError("Redundant bytes were found in Ethereum Invoke")
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ object EthereumTransaction {
Either.cond(
tokenAddress.isEmpty || EthEncoding.cleanHexPrefix(data).length == AssetDataLength,
(),
GenericError("unconsumed bytes remaining")
GenericError("Invalid asset data size for Ethereum Transfer")
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,25 @@ import com.wavesplatform.lang.directives.values.*
import com.wavesplatform.lang.script.Script
import com.wavesplatform.lang.script.v1.ExprScript
import com.wavesplatform.lang.utils.*
import com.wavesplatform.lang.v1.compiler.Terms.CONST_BOOLEAN
import com.wavesplatform.lang.v1.ContractLimits.MaxExprSizeInBytes
import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BOOLEAN, EXPR}
import com.wavesplatform.lang.v1.compiler.{ExpressionCompiler, TestCompiler}
import com.wavesplatform.lang.v1.parser.Parser
import com.wavesplatform.lang.v1.parser.Parser.LibrariesOffset.NoLibraries
import com.wavesplatform.settings.{FunctionalitySettings, TestFunctionalitySettings}
import com.wavesplatform.state.*
import com.wavesplatform.state.diffs.TransactionDiffer.TransactionValidationError
import com.wavesplatform.state.diffs.smart.smartEnabledFS
import com.wavesplatform.test.*
import com.wavesplatform.test.DomainPresets.*
import com.wavesplatform.transaction.Asset.IssuedAsset
import com.wavesplatform.transaction.TxValidationError.GenericError
import com.wavesplatform.transaction.assets.*
import com.wavesplatform.transaction.transfer.*
import com.wavesplatform.transaction.{GenesisTransaction, Transaction, TxHelpers, TxVersion}
import com.wavesplatform.{BlocksTransactionsHelpers, TestValues}
import fastparse.Parsed
import monix.eval.Coeval

class AssetTransactionsDiffTest extends PropSpec with BlocksTransactionsHelpers with WithDomain {

Expand Down Expand Up @@ -551,6 +555,56 @@ class AssetTransactionsDiffTest extends PropSpec with BlocksTransactionsHelpers
}
}

property(
s"Asset script size should be less than $MaxExprSizeInBytes after ${BlockchainFeatures.BlockRewardDistribution} activation"
) {
def scriptWithSize(size: Int): Script = new ExprScript {
val stdLibVersion: StdLibVersion = V6
val isFreeCall: Boolean = false
val expr: EXPR = TxHelpers.exprScript(V6)("true").expr
val bytes: Coeval[ByteStr] = Coeval(ByteStr(new Array[Byte](size)))
val containsBlockV2: Coeval[Boolean] = Coeval(false)
val containsArray: Boolean = false
}

val issuer = TxHelpers.signer(1)

withDomain(
ConsensusImprovements.setFeaturesHeight(BlockchainFeatures.BlockRewardDistribution -> 5),
AddrWithBalance.enoughBalances(issuer)
) { d =>
val issue = TxHelpers.issue(issuer, name = "asset1", script = Some(TestCompiler(V6).compileAsset("true")))

val issueWithBigScript: String => IssueTransaction =
name => TxHelpers.issue(issuer, name = name, script = Some(scriptWithSize(MaxExprSizeInBytes + 1)))
val updateWithBigScript = () => TxHelpers.setAssetScript(issuer, issue.asset, scriptWithSize(MaxExprSizeInBytes + 1))

d.appendBlock(issue)
d.appendAndAssertSucceed(issueWithBigScript("asset2"))
d.appendAndAssertSucceed(updateWithBigScript())

d.blockchain.isFeatureActivated(BlockchainFeatures.BlockRewardDistribution, d.blockchain.height + 1) shouldBe true

val errorMsg = s"Script is too large: ${MaxExprSizeInBytes + 1} bytes > $MaxExprSizeInBytes bytes"

// activation height
val invalidIssue = issueWithBigScript("asset3")
d.appendAndCatchError(invalidIssue) shouldBe TransactionValidationError(
GenericError(errorMsg),
invalidIssue
)
val invalidSetAssetScript = updateWithBigScript()
d.appendAndCatchError(invalidSetAssetScript) shouldBe TransactionValidationError(
GenericError(errorMsg),
invalidSetAssetScript
)
d.appendAndAssertSucceed(
TxHelpers.issue(issuer, name = "asset3", script = Some(scriptWithSize(MaxExprSizeInBytes))),
TxHelpers.setAssetScript(issuer, issue.asset, scriptWithSize(MaxExprSizeInBytes))
)
}
}

private def getScriptWithSyncCall(syncCall: String): ExprScript = {
val expr =
s"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ import com.wavesplatform.transaction.EthereumTransaction.Transfer
import com.wavesplatform.transaction.TxValidationError.GenericError
import com.wavesplatform.transaction.smart.InvokeScriptTransaction
import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment
import com.wavesplatform.transaction.{EthTxGenerator, EthereumTransaction, TxHelpers}
import com.wavesplatform.transaction.{Asset, EthTxGenerator, EthereumTransaction, TxHelpers}
import com.wavesplatform.transaction.utils.EthConverters.*
import EthTxGenerator.Arg
import com.wavesplatform.lang.v1.ContractLimits.MaxInvokeScriptSizeInBytes
import com.wavesplatform.state.diffs.TransactionDiffer.TransactionValidationError
import com.wavesplatform.utils.{DiffMatchers, EthEncoding, JsonMatchers}
import org.web3j.crypto.{Bip32ECKeyPair, RawTransaction}
import play.api.libs.json.Json
Expand Down Expand Up @@ -639,4 +641,61 @@ class EthereumTransactionDiffTest extends FlatSpec with WithDomain with DiffMatc
}
}

"Ethereum Transaction" should s"be checked for max allowed size after ${BlockchainFeatures.BlockRewardDistribution} activation" in {
val dApp = TxHelpers.signer(1)
val ethSigner = TxHelpers.signer(2).toEthKeyPair
val issuer = TxHelpers.signer(3)

def invokeWithSize(size: Int): EthereumTransaction = {
// 46 is for the dApp address and PB fields data
EthTxGenerator.generateEthInvoke(ethSigner, dApp.toAddress, "foo", Seq(Arg.Str("1" * (size - 46))), Seq.empty)
}

def invoke(withRedundantBytes: Boolean): EthereumTransaction =
EthTxGenerator.generateEthInvoke(ethSigner, dApp.toAddress, "foo", Seq(Arg.Str("1")), Seq.empty, withRedundantBytes = withRedundantBytes)

def transfer(asset: Asset, withRedundantBytes: Boolean): EthereumTransaction =
EthTxGenerator.generateEthTransfer(ethSigner, issuer.toAddress, 1, asset, withRedundantBytes = withRedundantBytes)

withDomain(
ConsensusImprovements.setFeaturesHeight(BlockchainFeatures.BlockRewardDistribution -> 4),
Seq(AddrWithBalance(dApp.toAddress), AddrWithBalance(ethSigner.toWavesAddress), AddrWithBalance(issuer.toAddress))
) { d =>
val script = TestCompiler(V6).compileContract(
"""
|@Callable(i)
|func foo(s: String) = []
|""".stripMargin
)
val issue = TxHelpers.issue(issuer)
d.appendBlock(TxHelpers.setScript(dApp, script), issue, TxHelpers.transfer(issuer, ethSigner.toWavesAddress, 10, issue.asset))
d.appendAndAssertSucceed(
invokeWithSize(MaxInvokeScriptSizeInBytes),
invokeWithSize(MaxInvokeScriptSizeInBytes + 1),
invoke(false),
invoke(true),
transfer(issue.asset, withRedundantBytes = false),
transfer(issue.asset, withRedundantBytes = true)
)

d.blockchain.isFeatureActivated(BlockchainFeatures.BlockRewardDistribution, d.blockchain.height + 1) shouldBe true
// activation height
val bigSizeInvoke = invokeWithSize(MaxInvokeScriptSizeInBytes + 1)
d.appendAndCatchError(bigSizeInvoke) shouldBe TransactionValidationError(
GenericError(s"Ethereum Invoke bytes length exceeds limit = $MaxInvokeScriptSizeInBytes"),
bigSizeInvoke
)
val redundantBytesInvoke = invoke(true)
d.appendAndCatchError(redundantBytesInvoke) shouldBe TransactionValidationError(
GenericError("Redundant bytes were found in Ethereum Invoke"),
redundantBytesInvoke
)
val redundantBytesTransfer = transfer(issue.asset, withRedundantBytes = true)
d.appendAndCatchError(redundantBytesTransfer) shouldBe TransactionValidationError(
GenericError("Invalid asset data size for Ethereum Transfer"),
redundantBytesTransfer
)
d.appendAndAssertSucceed(invokeWithSize(MaxInvokeScriptSizeInBytes), invoke(false), transfer(issue.asset, withRedundantBytes = false))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ import com.wavesplatform.state.diffs.FeeValidation.{FeeConstants, FeeUnit}
import com.wavesplatform.transaction.TransactionType.Transfer
import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment
import com.wavesplatform.utils.EthEncoding
import org.web3j.abi.FunctionEncoder
import org.web3j.abi.FunctionEncoder.{buildMethodId, buildMethodSignature}
import org.web3j.abi.datatypes.{AbiTypes, StructType}
import org.web3j.abi.{DefaultFunctionEncoder, FunctionEncoder, TypeEncoder}
import org.web3j.crypto.*

import java.math.BigInteger
import java.util

object EthTxGenerator {
sealed trait Arg
object Arg {
Expand Down Expand Up @@ -64,7 +68,8 @@ object EthTxGenerator {
recipient: Address,
amount: Long,
asset: Asset,
fee: Long = FeeConstants(Transfer) * FeeUnit
fee: Long = FeeConstants(Transfer) * FeeUnit,
withRedundantBytes: Boolean = false
): EthereumTransaction = asset match {
case Asset.Waves =>
signRawTransaction(keyPair, recipient.chainId)(
Expand All @@ -89,13 +94,15 @@ object EthTxGenerator {
Nil.asJava
)

val additionalData = if (withRedundantBytes) "aa" else ""

signRawTransaction(keyPair, recipient.chainId)(
RawTransaction.createTransaction(
BigInt(System.currentTimeMillis()).bigInteger, // nonce
EthereumTransaction.GasPrice,
BigInt(fee).bigInteger, // fee
EthEncoding.toHexString(assetId.arr.take(20)), // to (asset erc20 "contract" address)
FunctionEncoder.encode(function) // data
BigInt(fee).bigInteger, // fee
EthEncoding.toHexString(assetId.arr.take(20)), // to (asset erc20 "contract" address)
FunctionEncoder.encode(function) + additionalData // data
)
)
}
Expand All @@ -106,7 +113,8 @@ object EthTxGenerator {
funcName: String,
args: Seq[Arg],
payments: Seq[Payment],
fee: Long = 500000
fee: Long = 500000,
withRedundantBytes: Boolean = false
): EthereumTransaction = {
import scala.jdk.CollectionConverters.*
val paymentsArg = {
Expand All @@ -129,14 +137,67 @@ object EthTxGenerator {
Nil.asJava
)

val encodedFunction = if (withRedundantBytes) {
redundantBytesFunctionEncoder.encodeFunction(function)
} else {
FunctionEncoder.encode(function)
}

signRawTransaction(keyPair, address.chainId)(
RawTransaction.createTransaction(
BigInt(System.currentTimeMillis()).bigInteger,
EthereumTransaction.GasPrice,
BigInt(fee).bigInteger,
EthEncoding.toHexString(address.publicKeyHash),
FunctionEncoder.encode(function)
encodedFunction
)
)
}

private def redundantBytesFunctionEncoder: DefaultFunctionEncoder = new DefaultFunctionEncoder {
import org.web3j.abi.datatypes as ethTypes

override def encodeFunction(function: ethTypes.Function): String = {
val parameters = function.getInputParameters

val methodSignature = buildMethodSignature(function.getName, parameters)
val methodId = buildMethodId(methodSignature)

val result = new StringBuilder(methodId)

encodeParameters(parameters, result)
}

private def isDynamic(parameter: ethTypes.Type[?]) =
parameter.isInstanceOf[ethTypes.DynamicBytes] || parameter.isInstanceOf[ethTypes.Utf8String] || parameter
.isInstanceOf[ethTypes.DynamicArray[?]] || (parameter
.isInstanceOf[ethTypes.StaticArray[?]] && classOf[ethTypes.DynamicStruct].isAssignableFrom(
parameter.asInstanceOf[ethTypes.StaticArray[?]].getComponentType
))

private def encodeParameters(parameters: util.List[ethTypes.Type[?]], result: StringBuilder): String = {
var dynamicDataOffset = getLength(parameters) * ethTypes.Type.MAX_BYTE_LENGTH + 1
val dynamicData = new StringBuilder
import scala.jdk.CollectionConverters.*
for (parameter <- parameters.asScala) {
val encodedValue = TypeEncoder.encode(parameter)
if (isDynamic(parameter)) {
val encodedDataOffset = TypeEncoder.encode(new ethTypes.Uint(BigInteger.valueOf(dynamicDataOffset)))
result.append(encodedDataOffset)
dynamicData.append(encodedValue)
dynamicDataOffset += encodedValue.length >> 1
} else result.append(encodedValue)
}
result.append("aa")
result.append(dynamicData)
result.toString
}

private def getLength(parameters: util.List[ethTypes.Type[?]]) = {
val cls = Class.forName("org.web3j.abi.DefaultFunctionEncoder")
val method = cls.getDeclaredMethod("getLength", classOf[util.List[ethTypes.Type[?]]])
method.setAccessible(true)
method.invoke(this, parameters).asInstanceOf[Int]
}
}
}

0 comments on commit e8e5c5d

Please sign in to comment.