hey @B_S ok so i knew i had a code which can push to a struct array attribute somewhere in this form and i did some diffing and found it. Not sure if this is wht you after but it is given below
pragma solidity ^0.8.4;
pragma abicoder v2;
contract Wallet {
// constants
uint8 constant public MAX_OWNERS = 10;
// enums
enum TransferState {
Deleted, // This state should be only reached when the Transfer item is deleted
Pending,
Ready, // Transient state
Completed,
Cancelled, // Transient state
Failed
}
// state variables
uint public approvalCount;
uint public id;
address[MAX_OWNERS] private owners_array; // Used to cleanup approvals (not worth it in termas of gas)
mapping(address => bool) private owners; // Used for onlyOwner modifier
mapping(uint => Transfer) public transfers; // Infinitely growing
// structs
struct Transfer {
address payable recepient;
uint amount;
uint creationTime;
uint approvalCount;
address[] approvers;
TransferState state;
}
// events
event ReportTransferState(
uint indexed id,
address indexed recepient,
uint amount,
TransferState indexed state
);
// modifiers
modifier onlyOwner() {
require(owners[msg.sender] == true, "Owner-only operation");
_;
}
modifier transferExists(uint _id) {
require(_id <= id, "Transaction doen't exist yet");
_;
}
// constructor
constructor(uint _approvalCount, address[] memory _owners) {
require(_owners.length <= MAX_OWNERS, "Too many owners");
require(_owners.length >= _approvalCount, "Approval count cannot exceed the number of owners");
for(uint i = 0; i < _owners.length; i++) {
owners[_owners[i]] = true;
owners_array[i] = _owners[i];
}
approvalCount = _approvalCount;
id = 0;
}
//public
function deposit () public payable {}
function getTransfers(uint _id) public view returns (Transfer memory) {
return transfers[_id];
}
function withdraw (uint amount) public payable onlyOwner {
payable(msg.sender).transfer(amount);
}
function getBalance () public view returns (uint) {
return address(this).balance;
}
// Should create a new Transfer object
// Only owner is allowed to create TransferState
// Do not create transfers if the wallet balance is lower than the specified amount
function createTransfer (address payable recepient, uint amount) public onlyOwner returns (uint){
require(address(this).balance >= amount, "Insufficient balance in Wallet");
// Create a new transfer object
// approvalCount is set to 1 because the creator is assumed to have approved the transfer he has created
Transfer memory new_transfer = Transfer(
recepient,
amount,
block.timestamp,
1,
new address[](0),
TransferState.Pending
);
//Register the new transfer
transfers[id] = new_transfer;
processState(id);
id++;
return id - 1;
}
// Approves existing transfers which are in Pending state
// Only owner can approve transfers
// Each address can approve transation only once
function approveTransfer (uint _id) public onlyOwner transferExists(_id) returns (uint){
require(transfers[_id].state == TransferState.Pending, "Only pending transfer can be approved");
// require(approvals[_id][msg.sender] == false, "This address already approved this transaction");
for (uint i = 0; i < transfers[_id].approvers.length; i++) {
if(transfers[_id].approvers[i] == msg.sender) revert();
}
// Change transfer states
transfers[_id].approvalCount++;
transfers[_id].approvers.push(msg.sender);
assert(
transfers[_id].approvalCount <= approvalCount &&
transfers[_id].approvalCount > 1
);
processState(_id);
return _id;
}
// Revokes approval from existing transfers which are in Pending state
// Only owner can revoke transfers
// Each address can revoke transation only if it has previously approved it
function revokeTransfer (uint _id) public onlyOwner transferExists(_id) returns (uint){
require(transfers[_id].state == TransferState.Pending, "Only pending transfer can be revoked");
for (uint i = 0; i < transfers[_id].approvers.length; i++) {
require(transfers[_id].approvers[i] == msg.sender);
}
// Change transfer states
transfers[_id].approvalCount--;
assert(
transfers[_id].approvalCount < approvalCount &&
transfers[_id].approvalCount >= 0
);
processState(_id);
return _id;
}
// Execute transfer that is in Ready state
function executeTransfer (uint _id) public payable onlyOwner transferExists(_id) returns (uint) {
require(
transfers[_id].state == TransferState.Ready ||
transfers[_id].state == TransferState.Failed,
"Only Ready or Failed states are allowed"
);
if (transfers[_id].recepient.send(transfers[_id].amount))
transfers[_id].state = TransferState.Completed;
else
transfers[_id].state = TransferState.Failed;
processState(_id);
return _id;
}
// Cancels transfer that is in Failed state
function cancelFailedTransfer (uint _id) public onlyOwner transferExists(_id) returns (uint) {
require(transfers[_id].state == TransferState.Failed, "Only Failed transfer can be cancelled");
transfers[_id].state == TransferState.Cancelled;
processState(_id);
return _id;
}
//private
function processState(uint _id) private {
Transfer storage t = transfers[_id];
if (t.state == TransferState.Pending) {
emit ReportTransferState(_id, t.recepient, t.amount, t.state);
if (t.approvalCount == approvalCount)
t.state = TransferState.Ready;
else if (t.approvalCount == 0)
t.state = TransferState.Cancelled;
}
if (t.state == TransferState.Ready) {
emit ReportTransferState(_id, t.recepient, t.amount, t.state);
executeTransfer(_id);
}
if (t.state == TransferState.Failed) {
emit ReportTransferState(_id, t.recepient, t.amount, t.state);
}
if (t.state == TransferState.Cancelled || t.state == TransferState.Completed) {
emit ReportTransferState(_id, t.recepient, t.amount, t.state);
// This delete reduces gas fee from 90642 to 49665
delete transfers[_id];
// Further deletion of approvals via iteration through owners_array
// only increases the gas fee to 69251
}
}
}
to save you someill explain the pushing to the struct array. so we initialise the struct like this this is the transfet struct
// structs
struct Transfer {
address payable recepient;
uint amount;
uint creationTime;
uint approvalCount;
address[] approvers;
TransferState state;
}
in this example i keep track of the approvers using an array initially empty and each time a user approces a transsction i append that address to the array and then make a requirement that the user approving is not in this array for that transaction id. to initalise the transfer struct in the create transfer function i use that new address[](0)
thing i mentioned esterday
function createTransfer (address payable recepient, uint amount) public onlyOwner returns (uint){
require(address(this).balance >= amount, "Insufficient balance in Wallet");
// Create a new transfer object
// approvalCount is set to 1 because the creator is assumed to have approved the transfer he has created
Transfer memory new_transfer = Transfer(
recepient,
amount,
block.timestamp,
1,
new address[](0),
TransferState.Pending
);
//Register the new transfer
transfers[id] = new_transfer;
processState(id);
id++;
return id - 1;
}
thuis allows us to initalise an empty array eah time we create a transfer but using tis form by creating a new empty array allows us to push to it anytime we want for any given transfer. consider the approve function where we do this
function approveTransfer (uint _id) public onlyOwner transferExists(_id) returns (uint){
require(transfers[_id].state == TransferState.Pending, "Only pending transfer can be approved");
// require(approvals[_id][msg.sender] == false, "This address already approved this transaction");
for (uint i = 0; i < transfers[_id].approvers.length; i++) {
if(transfers[_id].approvers[i] == msg.sender) revert();
}
// Change transfer states
transfers[_id].approvalCount++;
transfers[_id].approvers.push(msg.sender);
assert(
transfers[_id].approvalCount <= approvalCount &&
transfers[_id].approvalCount > 1
);
processState(_id);
return _id;
}
notice how i first require that
for (uint i = 0; i < transfers[_id].approvers.length; i++) { if(transfers[_id].approvers[i] == msg.sender) revert(); }
this prevents a user apprving twice. then to push the user to the approvals array if he hasnt alread approve we simply call transfer requests of a paticular id and push to the approvers. see the code above. so this is how you can push to arrays in structs. i know your examle is different but you can work around. however in terms of gas this is innefficen there is better ways such as mappings which prevent us from having to do for loop checks using arrays. you should try use loops as little as possible.
the best approach is to creatt a few mappings. an approval mapping that maps an user account to a paricular transaction to a true of flase output. then a isOwner mapping which maps a user to a true of false output. consider below
mapping(address => mapping(uint => bool) approvals
mapping(address => bool) isOwner;
mapping(address => uint) ownerIndex
//the double mapping lets us chdck if and address has approve a patricular transaction or not
//approvals[msg.sender][transactionID] = true or false
//the other two mappings are used to determine if a user is a walllet owner and saves us from doing
//for loop checks
we would init a struct like this
struct Transfer{
string ticker;
uint amount;
address sender;
address payable receiver;
uint approvals;
bool hasBeenSent;
uint id;
uint timeOfCreation;
}
we can be dyncamic in how we add owners so we can make an anowner and removeowner function consider below
function addUsers(address _owners) public onlyOwners
{
require(!isOwner[_owners], "casnnot add duplicate owners")
owners.push(_owners);
ownerIndex[_owners] = owners.length - 1;
//from the current array calculate the value of minimum consensus
limit = owners.length - 1;
}
//remove user require the address we pass in is the address were removing
function removeUser(address _user) public onlyOwners
{
require(isOwner[_user], "user not an owner")
owners[ownerIndex[_user]] = owners[owners.length - 1];
owners.pop();
limit= owners.length - 1;
}
see here we have no loops saves us gas. For the approval mapping we can set up the approval function so that we require approvals is false for the function caller (msg.sender) . this is just a small idea of beter optimisation fo ryou you can play around with these ideas yourself