Transaktionen von Ether zwischen Verträgen

Ich versuche herauszufinden, wie Transaktionen zwischen Verträgen funktionieren und was Sie tun können und was nicht. Ich möchte einen Vertrag haben, der eine logische Entscheidung darüber trifft, wen ich bezahlen soll, wenn ich von einem anderen Vertrag gestoßen werde.

Hier ist der Vertrag, mit dem ich versucht habe, dieses Verhalten zu emulieren:

pragma solidity ^0.4.17;

contract DecisionMaker{

    Sender sd;

    function someLogicToDecidePayment(address receiver, address sender) public {
        //Some logic then:
        sd = Sender(sender);
        sd.send_transfer(receiver, 1000);
    }

}

contract Sender {
    DecisionMaker dm;

    constructor() public payable{}

    function send(address _receiver) payable {
        _receiver.call.value(10000000).gas(20317)();
    }

    function send_transfer(address _receiver, uint _amount) payable public {
        _receiver.transfer(_amount);
    }

    function placeBuy(address receiver, address decisionMaker) public {
        dm = DecisionMaker(decisionMaker);
        dm.someLogicToDecidePayment(receiver, address(this));
    }


} 

contract Receiver {
    uint public balance = 0;

    constructor() public payable{}

    function () payable {
      balance += msg.value;
    }
}

Beim Versuch, placeBuy über den Sender Contract auszuführen. Dadurch wird eine Instanz von DecisionMaker erstellt, sodass die Funktion someLogicToDecidePayment() ausführen kann. Nach einiger Logik, um zu entscheiden, wen ich bezahlen soll, möchte ich, dass der Vertrag send_transfer vom Senders-Vertrag aus aufruft, um etwas Äther an den Empfängervertrag zu senden. Dies schlägt jedoch mit folgendem Fehler fehl:

VM error: revert.
revert  The transaction has been reverted to the initial state.
Note: The constructor should be payable if you send value.  Debug the transaction to get more information. 

Wenn ich statt send_transfer die Funktion send innerhalb des Sender Contracts verwenden soll, geht die Transaktion durch, aber der Saldo des Receivers wird nicht erhöht. Es scheint, dass ich bei der Verwendung von Verträgen keinen Wert mit einer Transaktion senden kann. Kann mir jemand helfen zu verstehen, warum ich Ether mit keiner dieser Methoden erfolgreich von einem Kontrakt auf einen anderen übertragen kann?

Vielen Dank im Voraus.

BEARBEITEN

Ich habe sowohl dem Sender als auch dem Empfänger einen kostenpflichtigen Konstruktor hinzugefügt, wie von Lượng vorgeschlagen. Leider war ich immer noch nicht in der Lage, Ether zwischen den Verträgen mit der Übertragungsfunktion - send_transfer() - zu senden, war aber erfolgreich mit der Methode send(). Ich habe mein Testskript für alle beigefügt, die dies in Zukunft testen möchten:

const assert = require('assert');
const ganache = require('ganache-cli');
const Web3 = require('web3');
const web3 = new Web3(ganache.provider());

const compiledDecision = require('../ethereum/build/DecisionMaker.json');
const compiledReceiver = require('../ethereum/build/Receiver.json');
const compiledSender = require('../ethereum/build/Sender.json');

let accounts;
let decision;
let sender;
let receiver;
let confirmation;
let post_balance;


beforeEach(async () => {
    accounts = await web3.eth.getAccounts();

    decision = await new web3.eth.Contract(JSON.parse(compiledDecision.interface))
        .deploy({ data: compiledDecision.bytecode })
        .send({ from: accounts[0], gas: '1999999' });

    receiver = await new web3.eth.Contract(JSON.parse(compiledReceiver.interface))
        .deploy({ data: compiledReceiver.bytecode })
        .send({ from: accounts[0], gas: '1999999' });

    sender = await new web3.eth.Contract(JSON.parse(compiledSender.interface))
        .deploy({ data: compiledSender.bytecode })
        .send({ from: accounts[0], gas: '1999999' });

});

describe('Testing Contracts', () => {
    it('Attempts to make a transaction from sender to receiver through DecisionMaker', async () =>{
        let pre_balance = await receiver.methods.balance().call();

        await sender.methods.deposit().send({
            from: accounts[0],
            gas: 1000000,
            value: 1000000
        });

        let senderBalance = await web3.eth.getBalance(sender.options.address);
        console.log('senders contract balance', senderBalance);
        console.log('pre balance of receiver (variable)', pre_balance);

        try{
        let confirmation= await sender.methods.placeBuy(receiver.options.address, decision.options.address).send({
            from: accounts[0],
            gas: 1999999
        })
        post_balance = await receiver.methods.balance().call();
        }catch(err){
        console.log(err);
        }

    let balanceOfAddress = await web3.eth.getBalance(receiver.options.address);
    senderBalance = await web3.eth.getBalance(sender.options.address);
    console.log('confirmation of transaction', confirmation);
    console.log('post balance of receiver (variable)', post_balance);
    console.log('balance of receiver contract address', balanceOfAddress);
    console.log('senders balance post transaction', senderBalance);

    console.log('finished test');
    });
});

Ich bin immer noch neugierig und würde mich freuen, wenn jemand einen Vorschlag machen könnte, warum die Funktion send_transfer () nicht funktioniert, aber die Funktion send () :)

Antworten (2)

The constructor should be payable if you send value.

Erstellen Sie einen kostenpflichtigen Konstruktor, wenn Ihr Smart Contract die kostenpflichtige Funktion verwendet. In diesem Fall hat Ihr Vertragsabsender den kostenpflichtigen Konstruktor nicht erstellt

Danke für Ihre Antwort. Ich habe versucht, einen kostenpflichtigen Konstruktor hinzuzufügen, und die Transaktion wird immer noch zurückgesetzt, während ich die Übertragungsmethode verwende, aber ich habe den Wert erfolgreich mit der benutzerdefinierten Methode receiver.call.value().gas() übertragen. Ich werde dies der Bearbeitung der Frage hinzufügen.

Ein paar Beobachtungen.

Ich denke nicht, dass es gut ist, sich darauf zu verlassen, dass der Empfänger eine benutzerdefinierte Fallback-Funktion hat, es sei denn, Sie sind sicher, dass alle Empfänger Verträge sind, die Sie kontrollieren. Es sind keine realistischen Erwartungen für die anderen Teilnehmer.

Ein weiteres Problem, auf das Sie stoßen, ist das 2.300-Gas-Stipendium, das mit den transferund sendMethoden verbunden ist. Diese sollen einen Wiedereintrittsangriff verhindern, indem sie den Angreifer aushungern. 2.300 ist genug, um die Gelder zu akzeptieren und zu emittieren und zu feiern, aber das war es auch schon.

Es reicht nicht aus, in den Speicher zu schreiben (5.000 zum Überschreiben von Nicht-Null und 20.000 für einen neuen Steckplatz). Folglich balances[msg.sender] += ...verursacht dies eine Situation ohne Gas.

Ich sehe zwei Möglichkeiten, damit umzugehen, und Sie können entscheiden, welcher Weg sich richtig anfühlt.

  1. Die Escrow-Methode: Der Sender leitet die Gelder an den Entscheider weiter, der sie entweder ausgibt oder zurücksendet.
  2. Die Genehmigungsmethode: Der Absender erhält ein Signal vom Entscheider und fährt dann mit der Ausgabe fort, wenn er genehmigt wurde.

Beide Wege können so codiert werden, dass es ausnahmslos funktioniert.

Der erste Weg erfordert eine unkonventionelle callSyntax, um etwas eth an Decider und das gesamte Gas weiterzuleiten. Vertrag aufrufen und Wert von Solidity senden

Entscheider geht dann:

// decide what to do
if(isGo) {
  receiver.transfer(msg.value);
} else :
  msg.sender.transfer(msg.value);
}

Der andere Weg: Spender sucht Zustimmung mit:

require(dm.approveThis(args, ...));
// you don't get here unless approveThis was true
receiver.transfer(amount);

Ich hoffe es hilft.