Possible bug detected after Testnet Lima hardfork

Hi everyone,

I think I may have detected bug on contract level after the hard fork. We at AMPnet have collection of contracts and some tests which were running fine before the fork. After the fork we made minor changes in contracts (only cosmetic ones) so that contracts could be compiled with new version of Sophia compiler. Contracts were deployed successfully but this time tests were failing.

We tried running them on top of local nodes(aeproject 1.0.2 + compiler) but also on top of officially available public compiler and testnet node. Results are the same. We have identified the issue, and it looks like require() function is messing with Call.caller value.

Therefore, we designed this tiny example that anyone should be able to reproduce.

RandomContract.aes

contract RandomContract =

    entrypoint get_address() : address = ak_2cG1dC4Ad5ut67ZwkABTfN463gu9eiZCydhBXNyhmPUJyaNiak

BugExample.aes

contract RandomContractInterface =
    entrypoint get_address : () => address

contract BugExample =

    datatype event =
        TestEvent(address)

    entrypoint bug_test(remote: RandomContractInterface) =
        Chain.event(TestEvent(Call.caller))
        require(Call.caller != remote.get_address(), "This changes Call.caller value")
        Chain.event(TestEvent(Call.caller))

bug_test function emits two events with the value of Call.caller. The value should be the same in both events (that was the case before hard fork). But it looks like require(...) line messes with this value. When event values are decoded results are different.

Here’s test example:

const fs = require('fs');
const path = require('path');
const { Universal, Crypto } = require('@aeternity/aepp-sdk')

describe("Bug Test", () => { 

    it('might be a bug', async () => {
        let client = await Universal({
            url: "https://sdk-testnet.aepps.com",
            internalUrl: "https://sdk-testnet.aepps.com",
            keypair: {
                publicKey: "ak_fUq2NesPXcYZ1CcqBcGC3StpdnQw3iVxMA3YSeCNAwfN4myQk",
                secretKey: "7c6e602a94f30e4ea7edabe4376314f69ba7eaa2f355ecedb339df847b6f0d80575f81ffb0a297b7725dc671da0b1769b1fc5cbe45385c7b5ad1fc2eaf1d609d"
            },
            networkId: "ae_uat",
            compilerUrl: "http://latest.compiler.aepps.com"
        })

        let randomContractSource = fs.readFileSync(path.join(__dirname, '..', 'contracts', 'RandomContract.aes'), 'utf8') 
        let randomContractInstance = await client.getContractInstance(randomContractSource)
        let randomContractDeployTx = await randomContractInstance.deploy()
        console.log("RandomContract deploy tx", randomContractDeployTx)

        let bugExampleSource = fs.readFileSync(path.join(__dirname, '..', 'contracts', 'BugExample.aes'), 'utf8')
        let bugExampleInstance = await client.getContractInstance(bugExampleSource)

        let deployTx = await bugExampleInstance.deploy()
        console.log("BugExample deploy tx", deployTx)
        
        let testCall = await bugExampleInstance.methods.bug_test(randomContractDeployTx.address)

        let beforeRequireEventValue = Crypto.addressFromDecimal(testCall.result.log[0].topics[1])
        console.log("Log[0] Call.caller value", beforeRequireEventValue)

        let afterRequireEventValue = Crypto.addressFromDecimal(testCall.result.log[1].topics[1])
        console.log("Log[1] Call.caller value", afterRequireEventValue) 
    })

})

After running this test, following output is produced:

ampnet-ae-contracts git:(master) ✗ aeproject test --path test/BugTest.js
===== Starting Tests =====


  Bug Test
RandomContract deploy tx { owner: 'ak_fUq2NesPXcYZ1CcqBcGC3StpdnQw3iVxMA3YSeCNAwfN4myQk',
  transaction: 'th_EtsHpbj7SJhaRLDEydfNjKRXr5ukQbn3WjVSW9BLh7Vvx22wh',
  address: 'ct_2jUBTA42RfeeHzYd8Hyq16opo5m7BVry9pc2zxgW1UXKh9QPv6',
  createdAt: 2019-10-17T17:08:04.076Z,
  result:
   { callerId: 'ak_fUq2NesPXcYZ1CcqBcGC3StpdnQw3iVxMA3YSeCNAwfN4myQk',
     callerNonce: 300,
     contractId: 'ct_2jUBTA42RfeeHzYd8Hyq16opo5m7BVry9pc2zxgW1UXKh9QPv6',
     gasPrice: 1000000000,
     gasUsed: 56,
     height: 154943,
     log: [],
     returnType: 'ok',
     returnValue: 'cb_Xfbg4g==' },
  rawTx:
   'tx_+QEkCwH4QrhAnb51Fz7usGBgM27RGs9r5klnpC6hnfNB1cYBB0cAkCQ7AJITcvfUThHeU5d/67ZNjrgxZXBqaqVLzn/OlQ3KDrjc+NoqAaEBV1+B/7Cil7dyXcZx2gsXabH8XL5FOFx7WtH8Lq8dYJ2CASy4kviQRgOgB6261C27UNQyYPL10R2Rn+9ppOcXh57YTEtBQId4qUjAuGO4QP5E1kQfADcANwAaDoI/AQM//tJZ97oANwBHAAEDnwCg08FqSWIcHWfUGYkQVVqnai2N4sjUrwJCobShuIV4TA6dLwIRRNZEHxFpbml0EdJZ97otZ2V0X2FkZHJlc3OCLwCFNC4wLjAAgwUAA4ZINsDLkAAAAACDGBf4hDuaygCHKxFE1kQfP+FYfH4=' }
BugExample deploy tx { owner: 'ak_fUq2NesPXcYZ1CcqBcGC3StpdnQw3iVxMA3YSeCNAwfN4myQk',
  transaction: 'th_Tsj3CZ4pkrz514Ujz4oR4JLiCwhoX3psWxTSqbDwa5DRjgusc',
  address: 'ct_2bXvoCbBHzdDs5GizsckyJ3T1X1hV2ZHAcW9ejpKcq5YJ7jqA5',
  createdAt: 2019-10-17T17:08:11.947Z,
  result:
   { callerId: 'ak_fUq2NesPXcYZ1CcqBcGC3StpdnQw3iVxMA3YSeCNAwfN4myQk',
     callerNonce: 301,
     contractId: 'ct_2bXvoCbBHzdDs5GizsckyJ3T1X1hV2ZHAcW9ejpKcq5YJ7jqA5',
     gasPrice: 1000000000,
     gasUsed: 56,
     height: 154943,
     log: [],
     returnType: 'ok',
     returnValue: 'cb_Xfbg4g==' },
  rawTx:
   'tx_+QHACwH4QrhAOBIhNBy74tlEKDAla+7KJSSA+sO2o7/N1gxTAR3vCysKPdsI1Zuz48Qg2IGoO/iEOLBieWdMaJ+LQYD6ZbEIBrkBd/kBdCoBoQFXX4H/sKKXt3JdxnHaCxdpsfxcvkU4XHta0fwurx1gnYIBLbkBK/kBKEYDoK9dwncrLlNmaT46X9EyL9EYEE/tyKCYeNBzW7gJawsFwLj7uMr+RNZEHwA3ADcAGg6CPwEDP/5lpeAPAjcBhwE3AUcANwBGNAAADAOfAYGBsnETzY6sSLzsVWOTq0rhDZJgAknMsDPbq286C2kz+QwDX2IAAQM//mzEoF8ANwFHAjcAVQBE/BMCAAICAxFlpeAPGgJvgibPDAMADAEAAwD8EdJZ97o3AEcAVQAjAAcMCgwDeVRoaXMgY2hhbmdlcyBDYWxsLmNhbGxlciB2YWx1ZfsAGgJvgibPVQBE/BMCAAIEAxFlpeAPDAM/BgMIqy8DEUTWRB8RaW5pdBFlpeAPLUNoYWluLmV2ZW50EWzEoF8hYnVnX3Rlc3SCLwCFNC4wLjAAgwUAA4ZLCIcxqAAAAACDGBf4hDuaygCHKxFE1kQfP5CYZBg=' }
Log[0] Call.caller value ak_2jUBTA42RfeeHzYd8Hyq16opo5m7BVry9pc2zxgW1UXKh9QPv6
Log[1] Call.caller value ak_fUq2NesPXcYZ1CcqBcGC3StpdnQw3iVxMA3YSeCNAwfN4myQk

So in one log entry Call.caller value is correct (ak_fUq…) but in another it’s different which is something we found weird. Not only is another value different, but it is actually equal to address of deployed RandomContract. Take a closer look on output of RandomContract deploy transaction data. Contract id is ct_2jUB… which is exactly the value of wrong Call.caller from decoded logs (ak_2jUB…).

We didn’t know if this behaviour is intentional because something critical has changed with the new hard fork in the working of either require() function or Call.caller value, or in the other case, this behaviour represents a bug.

Can someone check these examples and shed some light on what could be the issue here?

Thanks

2 Likes

Forwarded your post to relevant people in the team, you should get an answer by tomorrow.

1 Like

This could be a bug, not sure, but for case like this is better to use Call.origin to be sure that such thing doesn’t happen (even if its a bug).

  • Call.origin is the address of the account that signed the call transaction that led to this call.
  • Call.caller is the address of the entity (possibly another contract) calling the contract.

Using Call.origin your test case returns what is intended:

$ ~apmtest:  aeproject test --path test/bugTest.js
===== Starting Tests =====


  Bug Test
RandomContract deploy tx { owner: 'ak_fUq2NesPXcYZ1CcqBcGC3StpdnQw3iVxMA3YSeCNAwfN4myQk',
  transaction: 'th_2bskvEB83w3CZ4vgE3pwG8Fk721BWo2CnbD8m6Jhk82AmSStA9',
  address: 'ct_bbeU5anfw3M9izFhnjY8MLkuGUVrbRPGWGtcAe8K2jy7PeM6T',
  createdAt: 2019-10-17T18:41:28.464Z,
  result:
   { callerId: 'ak_fUq2NesPXcYZ1CcqBcGC3StpdnQw3iVxMA3YSeCNAwfN4myQk',
     callerNonce: 304,
     contractId: 'ct_bbeU5anfw3M9izFhnjY8MLkuGUVrbRPGWGtcAe8K2jy7PeM6T',
     gasPrice: 1000000000,
     gasUsed: 56,
     height: 154972,
     log: [],
     returnType: 'ok',
     returnValue: 'cb_Xfbg4g==' },
  rawTx:
   'tx_+QEkCwH4QrhALy4EWaDnWRsge8CbRhQN0dy8VObo1cvE+okhkb+kQvrH++DmdNxPtijPL1xfe48Omj0A2pJWfHUFrAA9UN+tDbjc+NoqAaEBV1+B/7Cil7dyXcZx2gsXabH8XL5FOFx7WtH8Lq8dYJ2CATC4kviQRgOgB6261C27UNQyYPL10R2Rn+9ppOcXh57YTEtBQId4qUjAuGO4QP5E1kQfADcANwAaDoI/AQM//tJZ97oANwBHAAEDnwCg08FqSWIcHWfUGYkQVVqnai2N4sjUrwJCobShuIV4TA6dLwIRRNZEHxFpbml0EdJZ97otZ2V0X2FkZHJlc3OCLwCFNC4wLjAAgwUAA4ZINsDLkAAAAACDGBf4hDuaygCHKxFE1kQfP4gakqs=' }
BugExample deploy tx { owner: 'ak_fUq2NesPXcYZ1CcqBcGC3StpdnQw3iVxMA3YSeCNAwfN4myQk',
  transaction: 'th_2BpYz4c44r5ji6sr56X4vUvcxqkEWk6bkXjyyar6Y9XYodAeFY',
  address: 'ct_2PcZc9jcBd7GNYg5Uvh3GAnmmSbMo82ycuQKiA1ZknDipSujHo',
  createdAt: 2019-10-17T18:41:36.818Z,
  result:
   { callerId: 'ak_fUq2NesPXcYZ1CcqBcGC3StpdnQw3iVxMA3YSeCNAwfN4myQk',
     callerNonce: 305,
     contractId: 'ct_2PcZc9jcBd7GNYg5Uvh3GAnmmSbMo82ycuQKiA1ZknDipSujHo',
     gasPrice: 1000000000,
     gasUsed: 56,
     height: 154972,
     log: [],
     returnType: 'ok',
     returnValue: 'cb_Xfbg4g==' },
  rawTx:
   'tx_+QHACwH4QrhAbvJSnvDzh3Ns0XfPvCmF+Dr5dTqOM5t3+2EKJuNop1zewr9nW7xeGVlJjCKE68+KhxEyTyLgg138CWL0eyAICLkBd/kBdCoBoQFXX4H/sKKXt3JdxnHaCxdpsfxcvkU4XHta0fwurx1gnYIBMbkBK/kBKEYDoJYo+Oqg6OUOpOXthb8dncGIfga+uRvd38T+buUQhS5YwLj7uMr+RNZEHwA3ADcAGg6CPwEDP/5lpeAPAjcBhwE3AUcANwBGNAAADAOfAYGBsnETzY6sSLzsVWOTq0rhDZJgAknMsDPbq286C2kz+QwDX2IAAQM//mzEoF8ANwFHAjcAVABE/BMCAAICAxFlpeAPGgJvgibPDAMADAEAAwD8EdJZ97o3AEcAVQAjAAcMCgwDeVRoaXMgY2hhbmdlcyBDYWxsLmNhbGxlciB2YWx1ZfsAGgJvgibPVABE/BMCAAIEAxFlpeAPDAM/BgMIqy8DEUTWRB8RaW5pdBFlpeAPLUNoYWluLmV2ZW50EWzEoF8hYnVnX3Rlc3SCLwCFNC4wLjAAgwUAA4ZLCIcxqAAAAACDGBf4hDuaygCHKxFE1kQfP1Zxpg4=' }
Log[0] Call.caller value ak_fUq2NesPXcYZ1CcqBcGC3StpdnQw3iVxMA3YSeCNAwfN4myQk
Log[1] Call.caller value ak_fUq2NesPXcYZ1CcqBcGC3StpdnQw3iVxMA3YSeCNAwfN4myQk
    ✓ might be a bug (20121ms)

That’s something that is going to work for sure, we’re aware of that option. But this should be addressed nontheless. It’s unexpected behaviour, atleast when comparing to the node implementation before this hard fork. Thanks!

1 Like

It is already addressed and the core team are looking into it. Thanks!

1 Like

We’ve had an initial look, and can confirm that there is a bug in the FATE VM. The bug is not at all related to the use of require() - rather the problem seem to be that Call.caller is not handled properly when the VM returns from a remote contract call.

The AEVM is not affected by this bug - it is only when using FATE that there is a problem with Call.caller and as observed above Call.origin is also not affected.

4 Likes

I guess that said, and thank you for the explanation, it is great that you’ve pointed this out and its definetly worth a little bounty @filip - please add an aeternity address to your signature and i’ll ping the aeternity crypto foundation to send you and your team a little thank you.

Best
Emin

1 Like

Thanks @emin ! :metal:

Our Ae wallet is:
ak_2TkWi1xpea5NhWB8kBGvWTQrc1k8nxJ6jq2ANNNHYH3HAioFdG
(also visible in my profile page)